chore: website

This commit is contained in:
Nathan Flurry 2026-01-28 01:44:19 -08:00
parent d1cbd20b83
commit 745c64149e
46 changed files with 2173 additions and 12 deletions

View file

@ -0,0 +1,114 @@
'use client';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ArrowRight, Terminal, Check } from 'lucide-react';
const CTA_TITLES = [
'Control any coding agent with one SDK.',
'Claude Code, Codex, OpenCode, Amp — unified.',
'Swap agents without refactoring.',
'Universal events. Universal sessions.',
'Stream, store, and replay agent transcripts.',
'Human-in-the-loop, built in.',
'One SDK. Every coding agent.',
'Deploy anywhere. Same API everywhere.',
];
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'>
<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'
>
Universal SDK for coding agents. <br className='hidden md:block' />
Control Claude Code, Codex, OpenCode, and Amp with one API.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className='flex flex-col items-center justify-center gap-4 sm:flex-row'
>
<a
href='/docs'
className='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-white/10 bg-white px-4 py-2 text-sm text-black subpixel-antialiased shadow-sm transition-colors hover:bg-zinc-200'
>
Read the Docs
<ArrowRight className='h-4 w-4' />
</a>
<CopyInstallButton />
</motion.div>
</div>
</section>
);
}

View file

@ -0,0 +1,89 @@
'use client';
import { useState } from 'react';
import { ChevronDown } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
const faqs = [
{
question: 'Does this replace the Vercel AI SDK?',
answer:
"No, they're complementary. AI SDK is for building chat interfaces and calling LLMs. This SDK is for controlling autonomous coding agents that write code and run commands. Use AI SDK for your UI, use this when you need an agent to actually code.",
},
{
question: 'Which coding agents are supported?',
answer:
'Claude Code, Codex, OpenCode, and Amp. The SDK normalizes their APIs so you can swap between them without changing your code.',
},
{
question: 'How is session data persisted?',
answer:
"Events stream in a universal JSON schema. Persist them anywhere. We have adapters for Postgres and ClickHouse, or use <a href='https://rivet.gg' target='_blank' rel='noopener noreferrer' class='text-orange-400 hover:underline'>Rivet Actors</a> for managed stateful storage.",
},
{
question: 'Can I run this locally or does it require a sandbox provider?',
answer:
"Both. Run locally for development, deploy to E2B, Daytona, Vercel, or Docker for production.",
},
{
question: 'Is this open source?',
answer:
"Yes, MIT licensed. Code is on GitHub.",
},
];
function FAQItem({ question, answer }: { question: string; answer: string }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="border-b border-white/5">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex w-full items-center justify-between py-5 text-left"
>
<span className="text-base font-medium text-white pr-4">{question}</span>
<ChevronDown
className={`h-5 w-5 shrink-0 text-zinc-500 transition-transform duration-200 ${
isOpen ? 'rotate-180' : ''
}`}
/>
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden"
>
<p className="pb-5 text-sm leading-relaxed text-zinc-400" dangerouslySetInnerHTML={{ __html: answer }} />
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export function FAQ() {
return (
<section className="relative overflow-hidden border-t border-white/5 py-24">
<div className="mx-auto max-w-3xl px-6">
<div className="mb-12 text-center">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white">
Frequently Asked Questions
</h2>
<p className="text-zinc-400">
Common questions about the Coding Agent SDK.
</p>
</div>
<div className="divide-y divide-white/5 rounded-2xl border border-white/5 bg-zinc-900/30 px-6">
{faqs.map((faq, index) => (
<FAQItem key={index} question={faq.question} answer={faq.answer} />
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,329 @@
'use client';
import { Workflow, Server, Database, Download, Globe } from 'lucide-react';
import { FeatureIcon } from './ui/FeatureIcon';
import { CopyButton } from './ui/CopyButton';
function AgentLogo({ name, color, src }: { name: string; color: string; src?: string }) {
return (
<div className="flex items-center gap-2 px-2 py-1 rounded bg-zinc-800/50 border border-white/5">
{src ? (
<img src={src} alt={name} className="w-4 h-4" style={{ filter: 'brightness(0) invert(1)' }} />
) : (
<div
className="w-4 h-4 rounded-sm flex items-center justify-center text-[8px] font-bold"
style={{ backgroundColor: `${color}20`, color }}
>
{name[0]}
</div>
)}
<span className="text-[10px] text-zinc-400">{name}</span>
</div>
);
}
function ProviderLogo({ name, src }: { name: string; src?: string }) {
return (
<div className="flex items-center gap-2 px-2 py-1 rounded bg-zinc-800/50 border border-white/5">
{src ? (
<img src={src} alt={name} className="h-3 w-auto" style={{ filter: 'brightness(0) invert(1)' }} />
) : (
<div className="w-4 h-4 rounded-sm flex items-center justify-center text-[8px] font-bold bg-blue-500/20 text-blue-400">
D
</div>
)}
<span className="text-[10px] text-zinc-400">{name}</span>
</div>
);
}
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-4 text-3xl font-medium tracking-tight text-white md:text-5xl">
Full feature coverage.
</h2>
<p className="text-lg leading-relaxed text-zinc-400">
Available as an HTTP API or TypeScript SDK.
</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">
Claude Code, Codex, OpenCode, and Amp each have different APIs. We provide a single,
unified interface to control them all.
</p>
</div>
<div className="mt-auto relative z-10 bg-black/50 rounded-xl border border-white/5 p-5 overflow-hidden">
<div className="relative w-full aspect-[16/9] bg-[#050505] rounded-xl border border-white/10 overflow-hidden flex items-center justify-center">
{/* Subtle Background Grid */}
<div className="absolute inset-0 opacity-[0.03] pointer-events-none"
style={{ backgroundImage: 'linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)', backgroundSize: '40px 40px' }} />
<svg viewBox="0 0 800 450" className="w-full h-full relative z-10">
<defs>
{/* Glow effect for active lines */}
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="2.5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
{/* Define curved paths with their respective brand colors */}
{(() => {
const curvedPaths = [
{ d: "M480 225 C540 225, 560 90, 620 90", label: "Claude", color: "#d97757" },
{ d: "M480 225 C540 225, 560 180, 620 180", label: "OpenAI", color: "#ffffff" },
{ d: "M480 225 C540 225, 560 270, 620 270", label: "OpenCode", color: "#10B981" },
{ d: "M480 225 C540 225, 560 360, 620 360", label: "Amp", color: "#F59E0B" }
];
return (
<>
{/* Connection Lines */}
<g className="stroke-zinc-800" fill="none" strokeWidth="1.5">
{/* App -> Agent (Straight) */}
<path d="M180 225 L320 225" strokeDasharray="4 4" />
{/* Agent -> Providers (Curved) */}
{curvedPaths.map((path, i) => (
<path key={i} d={path.d} strokeDasharray="4 4" />
))}
</g>
{/* High-Performance Tracers */}
{/* Blue Tracer: App to SDK */}
<circle r="2.5" fill="#3B82F6" filter="url(#glow)">
<animateMotion path="M180 225 L320 225" dur="1.2s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0;1;0" dur="1.2s" repeatCount="indefinite" />
</circle>
{/* Colored Tracers: SDK to Providers (following curves and matching brand colors) */}
{curvedPaths.map((path, i) => (
<circle key={i} r="2.5" fill={path.color} filter="url(#glow)">
<animateMotion path={path.d} dur="2s" begin={`${i * 0.4}s`} repeatCount="indefinite" />
<animate attributeName="opacity" values="0;1;0" dur="2s" begin={`${i * 0.4}s`} repeatCount="indefinite" />
</circle>
))}
</>
);
})()}
{/* Nodes */}
{/* App Node */}
<g transform="translate(80, 190)">
<rect width="100" height="70" rx="12" fill="#111" stroke="#333" strokeWidth="1" />
<text x="50" y="42" fill="#999" textAnchor="middle" fontSize="14" fontWeight="600" className="uppercase tracking-tighter">Client App</text>
</g>
{/* Central SDK Node */}
<g transform="translate(320, 180)">
<rect width="160" height="90" rx="14" fill="#18181B" stroke="#3B82F6" strokeWidth="2" />
<text x="80" y="52" fill="white" textAnchor="middle" fontSize="14" fontWeight="800">Sandbox Agent SDK</text>
</g>
{/* Provider Nodes with Logos - Vertical Layout (centered) */}
{/* Claude */}
<g transform="translate(620, 50)">
<rect width="140" height="80" rx="10" fill="#111" stroke="#222" strokeWidth="1" />
<foreignObject x="0" y="10" width="140" height="32">
<div className="flex justify-center">
<img src="/logos/claude.svg" alt="Claude" className="h-8 w-8" />
</div>
</foreignObject>
<text x="70" y="62" fill="#999" textAnchor="middle" fontSize="11" fontWeight="600">Claude Code</text>
</g>
{/* Codex */}
<g transform="translate(620, 140)">
<rect width="140" height="80" rx="10" fill="#111" stroke="#222" strokeWidth="1" />
<foreignObject x="0" y="10" width="140" height="32">
<div className="flex justify-center">
<svg className="h-8 w-8" 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" />
</svg>
</div>
</foreignObject>
<text x="70" y="62" fill="#999" textAnchor="middle" fontSize="11" fontWeight="600">Codex</text>
</g>
{/* OpenCode */}
<g transform="translate(620, 230)">
<rect width="140" height="80" rx="10" fill="#111" stroke="#222" strokeWidth="1" />
<foreignObject x="0" y="10" width="140" height="32">
<div className="flex justify-center">
<svg className="h-8 w-auto" viewBox="0 0 32 40" fill="none">
<path d="M24 32H8V16H24V32Z" fill="#4B4646"/>
<path d="M24 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#F1ECEC"/>
</svg>
</div>
</foreignObject>
<text x="70" y="62" fill="#999" textAnchor="middle" fontSize="11" fontWeight="600">OpenCode</text>
</g>
{/* Amp */}
<g transform="translate(620, 320)">
<rect width="140" height="80" rx="10" fill="#111" stroke="#222" strokeWidth="1" />
<foreignObject x="0" y="12" width="140" height="28">
<div className="flex justify-center">
<img src="/logos/amp.svg" alt="Amp" className="h-6 w-auto" style={{ filter: 'brightness(0) invert(1)' }} />
</div>
</foreignObject>
<text x="70" y="62" fill="#999" textAnchor="middle" fontSize="11" fontWeight="600">Amp</text>
</g>
</svg>
</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 anywhere. One command to bridge coding agents to your
application.
</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 server
</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 schema that covers all features of all agents. Includes tool calls, permission requests, file edits, etc.
</p>
</div>
{/* Automatic Agent Installation - 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={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>
<p className="text-zinc-400 text-sm leading-relaxed">
Agents are automatically installed on first use. No manual setup required.
</p>
</div>
<div className="mt-auto w-full relative z-10">
<div className="flex flex-wrap gap-2">
{['Claude Code', 'Codex', 'OpenCode', 'Amp'].map((agent) => (
<span
key={agent}
className="px-3 py-1.5 rounded-md bg-zinc-800/50 border border-white/5 text-xs font-mono text-zinc-400"
>
{agent}
</span>
))}
</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">
Run locally, in Docker, or deploy to E2B, Daytona, and Vercel. Same SDK everywhere.
</p>
<div className="mt-auto flex flex-wrap gap-2">
{['Local', 'Docker', 'E2B', 'Daytona', 'Vercel', 'Netlify'].map((provider) => (
<span
key={provider}
className="px-2 py-1 rounded-md bg-zinc-800/50 border border-white/5 text-[10px] font-mono text-zinc-400"
>
{provider}
</span>
))}
</div>
</div>
</div>
</div>
</section>
);
}

View file

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

View file

@ -0,0 +1,230 @@
'use client';
import { Code, Server, GitBranch } from 'lucide-react';
import { CopyButton } from './ui/CopyButton';
const sdkCodeRaw = `import { SandboxAgent } from "sandbox-agent";
const client = await SandboxAgent.start();
await client.createSession("my-session", {
agent: "claude-code",
});
await client.postMessage("my-session", {
message: "Hello, world!",
});
for await (const event of client.streamEvents("my-session")) {
console.log(event.type, event.data);
}`;
function SdkCodeHighlighted() {
return (
<pre className="overflow-x-auto p-3 font-mono text-[11px] leading-relaxed">
<code>
<span className="text-purple-400">import</span>
<span className="text-zinc-300">{" { "}</span>
<span className="text-white">SandboxAgent</span>
<span className="text-zinc-300">{" } "}</span>
<span className="text-purple-400">from</span>
<span className="text-zinc-300"> </span>
<span className="text-green-400">"sandbox-agent"</span>
<span className="text-zinc-300">;</span>
{"\n\n"}
<span className="text-purple-400">const</span>
<span className="text-zinc-300"> client = </span>
<span className="text-purple-400">await</span>
<span className="text-zinc-300"> SandboxAgent.</span>
<span className="text-blue-400">start</span>
<span className="text-zinc-300">();</span>
{"\n\n"}
<span className="text-purple-400">await</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">createSession</span>
<span className="text-zinc-300">(</span>
<span className="text-green-400">"my-session"</span>
<span className="text-zinc-300">{", {"}</span>
{"\n"}
<span className="text-zinc-300">{" agent: "}</span>
<span className="text-green-400">"claude-code"</span>
<span className="text-zinc-300">,</span>
{"\n"}
<span className="text-zinc-300">{"});"}</span>
{"\n\n"}
<span className="text-purple-400">await</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">postMessage</span>
<span className="text-zinc-300">(</span>
<span className="text-green-400">"my-session"</span>
<span className="text-zinc-300">{", {"}</span>
{"\n"}
<span className="text-zinc-300">{" message: "}</span>
<span className="text-green-400">"Hello, world!"</span>
<span className="text-zinc-300">,</span>
{"\n"}
<span className="text-zinc-300">{"});"}</span>
{"\n\n"}
<span className="text-purple-400">for await</span>
<span className="text-zinc-300"> (</span>
<span className="text-purple-400">const</span>
<span className="text-zinc-300"> event </span>
<span className="text-purple-400">of</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">streamEvents</span>
<span className="text-zinc-300">(</span>
<span className="text-green-400">"my-session"</span>
<span className="text-zinc-300">{")) {"}</span>
{"\n"}
<span className="text-zinc-300">{" console."}</span>
<span className="text-blue-400">log</span>
<span className="text-zinc-300">(event.type, event.data);</span>
{"\n"}
<span className="text-zinc-300">{"}"}</span>
</code>
</pre>
);
}
const sandboxCommand = `curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh`;
const sourceCommands = `git clone https://github.com/rivet-dev/sandbox-agent
cd sandbox-agent
cargo run -p sandbox-agent --release`;
export function GetStarted() {
return (
<section id="get-started" 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 text-center">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white md:text-5xl">
Get Started
</h2>
<p className="text-lg text-zinc-400">
Choose the installation method that works best for your use case.
</p>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{/* 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="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
<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="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-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>
<h3 className="text-lg font-semibold text-white">TypeScript SDK</h3>
<p className="text-xs text-zinc-500">Embed in your application</p>
</div>
</div>
<p className="relative z-10 mb-4 text-sm leading-relaxed text-zinc-400 min-h-[4.5rem]">
Import the TypeScript SDK directly into your Node or browser application. Full type safety and streaming support.
</p>
<div className="relative z-10 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="flex items-center justify-between border-b border-white/5 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">example.ts</span>
<CopyButton text={sdkCodeRaw} />
</div>
<SdkCodeHighlighted />
</div>
</div>
</div>
{/* Option 2: Sandbox */}
<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="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
<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="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-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>
<h3 className="text-lg font-semibold text-white">HTTP API</h3>
<p className="text-xs text-zinc-500">Run as a server</p>
</div>
</div>
<p className="relative z-10 mb-4 text-sm leading-relaxed text-zinc-400 min-h-[4.5rem]">
Run as an HTTP server and connect from any language. Deploy to E2B, Daytona, Vercel, or your own infrastructure.
</p>
<div className="relative z-10 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="flex items-center justify-between border-b border-white/5 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">terminal</span>
<CopyButton text={sandboxCommand} />
</div>
<pre className="overflow-x-auto p-3 font-mono text-[11px] leading-relaxed flex-1">
<code>
<span className="text-zinc-500">$ </span>
<span className="text-zinc-300">curl -fsSL \</span>
{"\n"}
<span className="text-zinc-300">{" "}</span>
<span className="text-green-400">https://releases.rivet.dev/sandbox-agent/latest/install.sh</span>
<span className="text-zinc-300"> | </span>
<span className="text-blue-400">sh</span>
</code>
</pre>
</div>
</div>
</div>
{/* Option 3: Build from 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="absolute left-0 right-0 top-0 z-10 h-[1px] bg-gradient-to-r from-transparent via-white/20 to-transparent" />
<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="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-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>
<h3 className="text-lg font-semibold text-white">Open Source</h3>
<p className="text-xs text-zinc-500">Full control</p>
</div>
</div>
<p className="relative z-10 mb-4 text-sm leading-relaxed text-zinc-400 min-h-[4.5rem]">
Clone the repo and build with Cargo. Customize, contribute, or embed directly in your Rust project.
</p>
<div className="relative z-10 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="flex items-center justify-between border-b border-white/5 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">terminal</span>
<CopyButton text={sourceCommands} />
</div>
<pre className="overflow-x-auto p-3 font-mono text-[11px] leading-relaxed flex-1">
<code>
<span className="text-zinc-500">$ </span>
<span className="text-blue-400">git clone</span>
<span className="text-zinc-300"> </span>
<span className="text-green-400">https://github.com/rivet-dev/sandbox-agent</span>
{"\n"}
<span className="text-zinc-500">$ </span>
<span className="text-blue-400">cd</span>
<span className="text-zinc-300"> sandbox-agent</span>
{"\n"}
<span className="text-zinc-500">$ </span>
<span className="text-blue-400">cargo run</span>
<span className="text-zinc-300"> -p sandbox-agent --release</span>
</code>
</pre>
</div>
</div>
</div>
</div>
</div>
</section>
);
}

View file

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

View file

@ -0,0 +1,136 @@
'use client';
import { useState } from 'react';
import { Terminal, Check, ArrowRight } from 'lucide-react';
const CopyInstallButton = () => {
const [copied, setCopied] = useState(false);
const installCommand = 'npx skills add https://sandboxagent.dev/docs';
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-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-5xl font-medium leading-[1.1] tracking-tighter text-white md:text-7xl">
Universal API for <br />
Coding Agents
</h1>
<p className="mt-8 text-xl text-zinc-400 leading-relaxed max-w-2xl mx-auto lg:mx-0">
One SDK to control Claude Code, Codex, OpenCode, and Amp. Unified events, session management, and human-in-the-loop. 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>
<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>
<pre className="overflow-x-auto p-6 font-mono text-sm leading-relaxed">
<code>
<span className="text-purple-400">const</span>
<span className="text-zinc-300"> agents = </span>
<span className="text-purple-400">await</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">listAgents</span>
<span className="text-zinc-300">();</span>
{"\n\n"}
<span className="text-purple-400">await</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">createSession</span>
<span className="text-zinc-300">(</span>
<span className="text-green-400">"demo"</span>
<span className="text-zinc-300">{", {"}</span>
{"\n"}
<span className="text-zinc-300">{" agent: "}</span>
<span className="text-green-400">"codex"</span>
<span className="text-zinc-300">,</span>
{"\n"}
<span className="text-zinc-300">{" agentMode: "}</span>
<span className="text-green-400">"default"</span>
<span className="text-zinc-300">,</span>
{"\n"}
<span className="text-zinc-300">{" permissionMode: "}</span>
<span className="text-green-400">"plan"</span>
<span className="text-zinc-300">,</span>
{"\n"}
<span className="text-zinc-300">{"});"}</span>
{"\n\n"}
<span className="text-purple-400">await</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">postMessage</span>
<span className="text-zinc-300">(</span>
<span className="text-green-400">"demo"</span>
<span className="text-zinc-300">{", { message: "}</span>
<span className="text-green-400">"Hello from the SDK."</span>
<span className="text-zinc-300">{" });"}</span>
{"\n\n"}
<span className="text-purple-400">for await</span>
<span className="text-zinc-300"> (</span>
<span className="text-purple-400">const</span>
<span className="text-zinc-300"> event </span>
<span className="text-purple-400">of</span>
<span className="text-zinc-300"> client.</span>
<span className="text-blue-400">streamEvents</span>
<span className="text-zinc-300">(</span>
<span className="text-green-400">"demo"</span>
<span className="text-zinc-300">{", { offset: "}</span>
<span className="text-amber-400">0</span>
<span className="text-zinc-300">{" })) {"}</span>
{"\n"}
<span className="text-zinc-300">{" console."}</span>
<span className="text-blue-400">log</span>
<span className="text-zinc-300">(event.type, event.data);</span>
{"\n"}
<span className="text-zinc-300">{"}"}</span>
</code>
</pre>
</div>
</div>
</div>
</div>
</div>
</section>
);
}

View file

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

View file

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

View file

@ -0,0 +1,217 @@
'use client';
import { motion } from 'framer-motion';
const frictions = [
{
number: '01',
title: 'Fragmented Agent Scaffolds',
description:
'Claude Code, Codex, OpenCode, and Amp each have proprietary APIs. Swapping agents means rewriting your entire integration.',
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:
"Whether you're running locally, in Docker, or with E2B, Daytona, and Vercel — you shouldn't need different integration code for each.",
solution: 'One SDK, any environment. Deploy locally or to any cloud provider 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 agent process ends. Debugging and replay become 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">
Integrating coding agents is hard.
</h2>
<p className="max-w-2xl text-lg leading-relaxed text-zinc-400">
Every agent has its own API, event format, and session model. Swapping agents means
rewriting your entire integration.
</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">
{/* Title */}
<h3 className="mb-3 text-xl font-medium text-white">{friction.title}</h3>
{/* Description */}
<p className="text-sm leading-relaxed text-zinc-500">{friction.description}</p>
{/* Solution */}
<div className="mt-4 border-t border-white/5 pt-4">
<p className="text-sm font-medium text-zinc-300">{friction.solution}</p>
</div>
{/* Visual */}
{friction.visual}
</div>
</motion.div>
);
})}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,53 @@
'use client';
import { Workflow, Database, Server } from 'lucide-react';
import { FeatureIcon } from './ui/FeatureIcon';
const problems = [
{
title: 'Universal Agent API',
desc: 'Claude Code, Codex, OpenCode, and Amp each have different APIs. We provide a single interface to control them all.',
icon: Workflow,
color: 'text-accent',
},
{
title: 'Universal Transcripts',
desc: 'Every agent has its own event format. Our universal schema normalizes them all — stream, store, and replay with ease.',
icon: Database,
color: 'text-purple-400',
},
{
title: 'Run Anywhere',
desc: 'Lightweight Rust daemon runs locally or in any environment. One command to bridge coding agents 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 Coding Agent SDK?</h2>
<p className="text-zinc-400 max-w-xl mx-auto">
Solving the three fundamental friction points of agentic software development.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{problems.map((item, idx) => (
<div
key={idx}
className="group p-8 rounded-2xl bg-zinc-900/40 border border-white/5 hover:border-accent/30 transition-all duration-300"
>
<FeatureIcon icon={item.icon} color={item.color} />
<h3 className="text-xl font-bold text-white mb-3">{item.title}</h3>
<p className="text-zinc-400 text-sm leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</div>
</section>
);
}

View file

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

View file

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

View file

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

View file

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