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 react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://sandbox-agent.dev',
output: 'static',
integrations: [
react(),
tailwind()
tailwind(),
sitemap()
]
});

View file

@ -13,6 +13,7 @@
},
"dependencies": {
"@astrojs/react": "^4.2.0",
"@astrojs/sitemap": "^3.2.0",
"@astrojs/tailwind": "^6.0.0",
"astro": "^5.1.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);
return (
<div className="border-b border-white/5">
<div className="border-t border-white/10 first:border-t-0">
<button
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
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' : ''
}`}
/>
@ -82,7 +82,7 @@ function FAQItem({ question, answer }: { question: string; answer: string }) {
transition={{ duration: 0.2 }}
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>
)}
</AnimatePresence>
@ -92,22 +92,40 @@ function FAQItem({ question, answer }: { question: string; answer: string }) {
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">
<section className="border-t border-white/10 py-48">
<div className="mx-auto max-w-7xl px-6">
<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
</h2>
<p className="text-zinc-400">
</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="mx-auto max-w-xl text-base leading-relaxed text-zinc-500"
>
Common questions about running agents in sandboxes.
</p>
</motion.p>
</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) => (
<FAQItem key={index} question={faq.question} answer={faq.answer} />
))}
</div>
</motion.div>
</div>
</section>
);

View file

@ -1,169 +1,120 @@
'use client';
import { motion } from 'framer-motion';
import { Workflow, Server, Database, Download, Globe, Plug } from 'lucide-react';
import { FeatureIcon } from './ui/FeatureIcon';
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">
<section id="features" className="border-t border-white/10 py-48">
<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"
>
How it works.
</h2>
<p className="text-lg leading-relaxed text-zinc-400">
</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-xl text-base leading-relaxed text-zinc-500"
>
A server runs inside your sandbox. Your app connects over HTTP to control any coding agent.
</p>
</motion.p>
</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 */}
<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">
{/* 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 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">
<div className="flex items-center gap-3">
<div className="text-zinc-500 transition-colors group-hover:text-orange-400">
<Workflow className="h-4 w-4" />
</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,
unified interface to control them all.
</p>
</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">
<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">Streaming Events</h4>
{/* Streaming Events */}
<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">
<div className="flex items-center gap-3">
<div className="text-zinc-500 transition-colors group-hover:text-green-400">
<Server className="h-4 w-4" />
</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.
</p>
</div>
{/* Handling Permissions */}
<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(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>
{/* Universal Schema */}
<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">
<div className="flex items-center gap-3">
<div className="text-zinc-500 transition-colors group-hover:text-purple-400">
<Database className="h-4 w-4" />
</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. Approve or deny tool executions remotely over HTTP.
<h4 className="text-base font-normal text-white">Universal Schema</h4>
</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>
</div>
{/* 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">
{/* 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">Runs Inside Any Sandbox</h4>
<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">
<div className="flex items-center gap-3">
<div className="text-zinc-500 transition-colors group-hover:text-blue-400">
<Globe className="h-4 w-4" />
</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.
</p>
</div>
{/* Automatic Agent Installation */}
<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">
{/* 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="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>
{/* Session Management */}
<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">
<div className="flex items-center gap-3">
<div className="text-zinc-500 transition-colors group-hover:text-amber-400">
<Download className="h-4 w-4" />
</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.
</p>
</div>
{/* 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">
{/* 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(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 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">
<div className="flex items-center gap-3">
<div className="text-zinc-500 transition-colors group-hover:text-pink-400">
<Plug className="h-4 w-4" />
</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.
</p>
</div>
</div>
</motion.div>
</div>
</section>
);

View file

@ -1,5 +1,7 @@
'use client';
import { motion } from 'framer-motion';
const footer = {
products: [
{ name: 'Actors', href: 'https://rivet.dev/docs/actors' },
@ -48,16 +50,22 @@ const footer = {
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">
<footer className="border-t border-white/10 bg-black">
<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">
{/* Logo & Social */}
<div className="space-y-6 xl:col-span-4">
<a href="https://rivet.dev">
<img src="/rivet-logo-text-white.svg" alt="Rivet" className="h-6 w-auto" />
<motion.div
initial={{ opacity: 0, y: 20 }}
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>
<p className="text-sm leading-6 text-zinc-400">
Build and scale stateful workloads
<p className="text-sm leading-6 text-zinc-500">
Infrastructure for software that thinks
</p>
<div className="flex space-x-4">
{footer.social.map((item) => (
@ -73,64 +81,87 @@ export function Footer() {
</a>
))}
</div>
</div>
</motion.div>
{/* Links */}
<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>
<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"
className="text-sm leading-6 text-zinc-500 hover:text-white transition-colors"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
</motion.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>
<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"
className="text-sm leading-6 text-zinc-500 hover:text-white transition-colors"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
</motion.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>
<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"
className="text-sm leading-6 text-zinc-500 hover:text-white transition-colors"
>
{item.name}
</a>
</li>
))}
</ul>
</div>
</motion.div>
</div>
</div>
{/* Bottom */}
<div className="mt-12 border-t border-white/10 pt-8">
<p className="text-xs text-zinc-500 text-center">
<motion.div
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.
</p>
</div>
</motion.div>
</div>
</footer>
);

View file

@ -1,5 +1,6 @@
'use client';
import { motion } from 'framer-motion';
import { Code, Server, GitBranch } from 'lucide-react';
import { CopyButton } from './ui/CopyButton';
@ -94,44 +95,55 @@ 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">
<section id="get-started" className="border-t border-white/10 py-48">
<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"
>
Get Started
</h2>
<p className="text-lg text-zinc-400">
</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-xl text-base leading-relaxed text-zinc-500"
>
Choose the installation method that works best for your use case.
</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>
</motion.p>
</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 */}
<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 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="mb-4 flex items-center gap-3">
<div className="text-zinc-500">
<Code className="h-4 w-4" />
</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>
</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.
</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">
<div className="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/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">example.ts</span>
<CopyButton text={sdkCodeRaw} />
</div>
@ -140,29 +152,25 @@ export function GetStarted() {
</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" />
{/* Option 2: HTTP API */}
<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="mb-4 flex items-center gap-3">
<div className="text-zinc-500">
<Server className="h-4 w-4" />
</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>
</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.
</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">
<div className="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/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">terminal</span>
<CopyButton text={sandboxCommand} />
</div>
@ -181,29 +189,25 @@ export function GetStarted() {
</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" />
{/* Option 3: Open Source */}
<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="mb-4 flex items-center gap-3">
<div className="text-zinc-500">
<GitBranch className="h-4 w-4" />
</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>
</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.
</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">
<div className="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/10 bg-white/5 px-3 py-2">
<span className="text-[10px] font-medium text-zinc-500">terminal</span>
<CopyButton text={sourceCommands} />
</div>
@ -226,7 +230,7 @@ export function GetStarted() {
</div>
</div>
</div>
</div>
</motion.div>
</div>
</section>
);

View file

@ -1,14 +1,15 @@
'use client';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Terminal, Check, ArrowRight } from 'lucide-react';
const ADAPTERS = [
{ label: 'Claude Code', color: '#D97757', x: 35, y: 30, logo: '/logos/claude.svg' },
{ label: 'Codex', color: '#10A37F', x: 185, y: 30, logo: 'openai' },
{ label: 'Amp', color: '#F59E0B', x: 35, y: 115, logo: '/logos/amp.svg' },
{ label: 'OpenCode', color: '#8B5CF6', x: 185, y: 115, logo: 'opencode' },
{ label: 'Pi', color: '#38BDF8', x: 110, y: 200, logo: '/logos/pi.svg' },
{ label: 'Claude Code', color: '#D97757', x: 20, y: 70, logo: '/logos/claude.svg' },
{ label: 'Codex', color: '#10A37F', x: 132, y: 70, logo: 'openai' },
{ label: 'Pi', color: '#06B6D4', x: 244, y: 70, logo: 'pi' },
{ label: 'Amp', color: '#F59E0B', x: 76, y: 155, logo: '/logos/amp.svg' },
{ label: 'OpenCode', color: '#8B5CF6', x: 188, y: 155, logo: 'opencode' },
];
function UniversalAPIDiagram() {
@ -22,23 +23,16 @@ function UniversalAPIDiagram() {
}, []);
return (
<div className="relative w-full aspect-[16/9] bg-[#050505] rounded-xl border border-white/10 overflow-hidden flex items-center justify-center">
{/* Background Grid */}
<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 Dots - color changes with active adapter */}
<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={{
backgroundImage:
'linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)',
backgroundSize: '40px 40px',
backgroundImage: `radial-gradient(circle, ${ADAPTERS[activeIndex].color} 1px, transparent 1px)`,
backgroundSize: '24px 24px',
}}
/>
{/* 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">
<defs>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
@ -50,13 +44,14 @@ function UniversalAPIDiagram() {
</filter>
</defs>
{/* YOUR APP NODE */}
<g transform="translate(60, 175)">
<rect width="180" height="100" rx="16" fill="#0A0A0A" stroke="#333" strokeWidth="2" />
<text x="90" y="55" fill="#FFFFFF" textAnchor="middle" fontSize="20" fontWeight="700">
Your App
</text>
</g>
{/* YOUR APP NODE - Glass dark effect with backdrop blur */}
<foreignObject x="60" y="175" width="180" height="100">
<div
className="w-full h-full rounded-2xl border border-white/10 bg-black/40 backdrop-blur-md flex items-center justify-center"
>
<span className="text-white text-xl font-bold">Your App</span>
</div>
</foreignObject>
{/* HTTP/SSE LINE */}
<g>
@ -74,21 +69,21 @@ function UniversalAPIDiagram() {
</text>
</g>
{/* SANDBOX BOUNDARY */}
<g transform="translate(360, 45)">
<rect width="380" height="360" rx="24" fill="#080808" stroke="#333" strokeWidth="1.5" />
<rect width="380" height="45" rx="12" fill="rgba(255,255,255,0.02)" />
<text x="190" y="28" fill="#FFFFFF" textAnchor="middle" fontSize="14" fontWeight="800" letterSpacing="0.2em">
{/* SANDBOX BOUNDARY - Glass dark effect with backdrop blur */}
<foreignObject x="360" y="45" width="410" height="360">
<div className="w-full h-full rounded-3xl border border-white/10 bg-black/40 backdrop-blur-md">
<div className="text-white text-sm font-extrabold tracking-[0.2em] text-center pt-4">
SANDBOX
</text>
</div>
</div>
</foreignObject>
{/* SANDBOX AGENT SDK */}
<g transform="translate(25, 65)">
<rect width="330" height="270" rx="20" fill="#0D0D0F" stroke="#3B82F6" strokeWidth="2" />
<text x="165" y="35" fill="#FFFFFF" textAnchor="middle" fontSize="18" fontWeight="800">
<g transform="translate(385, 110)">
<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="180" y="35" fill="#FFFFFF" textAnchor="middle" fontSize="18" fontWeight="800">
Sandbox Agent Server
</text>
<line x1="40" y1="50" x2="290" y2="50" stroke="#333" strokeWidth="1" />
{/* PROVIDER ADAPTERS */}
{ADAPTERS.map((p, i) => {
@ -96,33 +91,38 @@ function UniversalAPIDiagram() {
return (
<g key={i} transform={`translate(${p.x}, ${p.y})`}>
<rect
width="110"
height="65"
rx="12"
width="95"
height="58"
rx="10"
fill={isActive ? '#1A1A1E' : '#111'}
stroke={isActive ? p.color : '#333'}
strokeWidth={isActive ? 2 : 1.5}
/>
<g opacity={isActive ? 1 : 0.4}>
{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" />
</svg>
) : 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 8H8V32H24V8ZM32 40H0V0H32V40Z" fill="#F1ECEC"/>
</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>
<text
x="55"
y="52"
x="47.5"
y="46"
fill="#FFFFFF"
textAnchor="middle"
fontSize="11"
fontSize="10"
fontWeight="600"
opacity={isActive ? 1 : 0.4}
>
@ -134,11 +134,11 @@ function UniversalAPIDiagram() {
{/* Active Agent Label */}
<text
x="165"
x="180"
y="250"
fill={ADAPTERS[activeIndex].color}
textAnchor="middle"
fontSize="10"
fontSize="12"
fontWeight="800"
fontFamily="monospace"
letterSpacing="0.1em"
@ -146,7 +146,6 @@ function UniversalAPIDiagram() {
CONNECTED TO {ADAPTERS[activeIndex].label.toUpperCase()}
</text>
</g>
</g>
</svg>
</div>
);
@ -167,47 +166,104 @@ const CopyInstallButton = () => {
};
return (
<div className="relative group w-full sm:w-auto">
<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'
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}
</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() {
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-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>
const [scrollOpacity, setScrollOpacity] = useState(1);
<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
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
<ArrowRight className='h-4 w-4' />
<ArrowRight className="h-4 w-4" />
</a>
<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 className="flex-1 w-full max-w-2xl">
<UniversalAPIDiagram />
</div>
</div>
</div>
</section>
);
}

View file

@ -1,23 +1,45 @@
'use client';
import { motion } from 'framer-motion';
export function Inspector() {
return (
<section className="relative overflow-hidden border-t border-white/5 py-24">
<div className="mx-auto max-w-4xl px-6 text-center">
<h2 className="mb-4 text-3xl font-medium tracking-tight text-white md:text-5xl">
<section className="border-t border-white/10 py-48">
<div className="mx-auto max-w-7xl px-6">
<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
</h2>
<p className="mb-12 text-lg text-zinc-400">
Inspect sessions, view event payloads, and troubleshoot without writing code.
</p>
</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="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
src="/images/inspector.png"
alt="Sandbox Agent Inspector"
className="w-full"
/>
</div>
</motion.div>
</div>
</section>
);

View file

@ -6,11 +6,12 @@ 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">
<a
href={href}
className="px-3 py-2 text-sm font-medium text-zinc-400 transition-colors duration-200 hover:text-white"
>
{children}
</a>
</div>
);
}
@ -34,15 +35,17 @@ export function Navigation() {
isScrolled ? "before:border-white/10" : "before:border-transparent"
}`}
>
{/* Background with blur */}
<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"
: "bg-transparent 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 ${
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"
}`}
>
@ -53,24 +56,24 @@ export function Navigation() {
<a href="https://rivet.dev" className="flex items-center">
<img src="/rivet-icon.svg" alt="Rivet" className="size-8" />
</a>
<span className="text-white/30">|</span>
<span className="text-white/20">|</span>
<a href="/" className="flex items-center">
<img src="/logos/sandboxagent.svg" alt="Sandbox Agent SDK" className="h-6 w-auto" />
</a>
</div>
{/* 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="https://github.com/rivet-dev/sandbox-agent/releases">Changelog</NavItem>
</div>
</div>
{/* Right side */}
<div className="hidden md:flex flex-row items-center">
<div className="hidden md:flex flex-row items-center gap-2">
<a
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"
>
<svg
@ -90,7 +93,7 @@ export function Navigation() {
{/* Mobile menu 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)}
>
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
@ -101,26 +104,26 @@ export function Navigation() {
{/* 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">
<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-1">
<a
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)}
>
Docs
</a>
<a
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)}
>
Changelog
</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
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)}
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" />
</svg>
<span>Discord</span>
<span className="font-medium">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"
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)}
/>
</div>

View file

@ -1,133 +1,91 @@
'use client';
import { motion } from 'framer-motion';
import { X, Check } from 'lucide-react';
import { Shield, Layers, Database, X, Check } from 'lucide-react';
const frictions = [
{
number: '01',
icon: Shield,
title: 'Coding Agents Need Sandboxes',
problem:
"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.',
accentColor: 'orange',
},
{
number: '02',
icon: Layers,
title: 'Every Coding Agent is Different',
problem:
'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.',
accentColor: 'purple',
},
{
number: '03',
icon: Database,
title: 'Sessions Are Ephemeral',
problem:
'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.',
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">
<section className="border-t border-white/10 py-48">
<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
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
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">
Running coding agents remotely is hard.
</h2>
<p className="max-w-2xl text-lg leading-relaxed text-zinc-400">
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.
</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 flex flex-col h-full">
{/* Title */}
<h3 className="mb-4 text-xl font-medium text-white">{friction.title}</h3>
{/* Problem */}
{frictions.map((friction) => (
<div key={friction.title} className="flex flex-col border-t border-white/10 pt-6">
<div className="mb-3 text-zinc-500">
<friction.icon className="h-4 w-4" />
</div>
<h3 className="mb-4 text-base font-normal text-white">{friction.title}</h3>
<div className="mb-4">
<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="w-3 h-3 text-red-400" />
<X className="h-3 w-3 text-zinc-600" />
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Problem</span>
</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>
<p className="text-sm leading-relaxed text-zinc-500">{friction.problem}</p>
</div>
{/* Solution */}
<div className="mt-auto pt-4 border-t border-white/5">
<div className="mt-auto border-t border-white/5 pt-4">
<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="w-3 h-3 text-green-400" />
<Check className="h-3 w-3 text-green-400" />
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-400">Solution</span>
</div>
<span className="text-xs font-semibold uppercase tracking-wider text-green-400">Solution</span>
</div>
<p className="text-sm font-medium leading-relaxed text-zinc-300">{friction.solution}</p>
<p className="text-sm leading-relaxed text-zinc-300">
{friction.solution}
</p>
</div>
</div>
))}
</motion.div>
);
})}
</div>
</div>
</section>
);

View file

@ -4,32 +4,81 @@ interface Props {
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>
<html lang="en">
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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" />
<!-- Preconnect to font providers -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<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" />
<title>{title}</title>
<!-- Open Graph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<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:height" content="1260" />
<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: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>
<body class="min-h-screen">
<body class="min-h-screen bg-background text-foreground antialiased">
<slot />
</body>
</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.">
<div class="min-h-screen bg-black text-white selection:bg-accent/30">
<Navigation client:load />
<main>
<Hero client:load />
@ -22,5 +21,4 @@ import { Footer } from '../components/Footer';
<FAQ client:visible />
</main>
<Footer client:visible />
</div>
</Layout>

View file

@ -3,13 +3,61 @@
@tailwind utilities;
@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 {
@apply bg-black text-white antialiased;
font-family: 'Open Sans', system-ui, sans-serif;
}
/* Text selection - matches rivet.dev */
::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 */
@ -65,6 +113,7 @@
}
@layer components {
/* Glass morphism effects */
.glass {
@apply bg-white/[0.02] backdrop-blur-md border border-white/10;
}
@ -72,4 +121,123 @@
.glass-hover {
@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: {
extend: {
colors: {
// Primary accent (OrangeRed)
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: {
sans: ['Open Sans', 'system-ui', 'sans-serif'],
heading: ['Satoshi', 'Open Sans', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
animation: {
'fade-in-up': 'fade-in-up 0.6s ease-out forwards',
'pulse-slow': 'pulse 3s ease-in-out infinite',
'fade-in-up': 'fade-in-up 0.8s ease-out forwards',
'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: {
'fade-in-up': {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
from: { opacity: '0', transform: 'translateY(24px)' },
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