mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 07:04:48 +00:00
feat(inspector): update visual styling to match landing page (#166)
feat(inspector): update visual styling to match landing page - Update color scheme to match website (black bg, white/10 borders) - Add Open Sans font - Update button styles (white primary buttons) - Add collapsible tool results and status messages - Replace avatar letters with icons (User, Settings, AlertTriangle) - Add status dividers for session/turn events - Update feature coverage badges to lighter grey - Remove pill styling from event times - Update popup menus to solid black background feat(website): add Pi agent to hero diagram and update styling - Add Pi agent with cyan color (#06B6D4) to the diagram - Update layout to 3 agents on top row, 2 on bottom row - Add backdrop-blur glass effects for modern look - Add animated dot background that changes with active adapter - Add scroll fade effect for hero section - Update subtitle to include Pi in supported agents list - Increase 'CONNECTED TO' label font size feat(website): add site styling updates and SEO improvements - Update component styling to match Rivet design (FAQ, FeatureGrid, etc.) - Add SEO improvements (sitemap, robots.txt, meta tags, Open Graph) - Remove CTASection component - Update footer tagline - Add Pi logo
This commit is contained in:
parent
89933c5f80
commit
cdbe920070
19 changed files with 1158 additions and 867 deletions
|
|
@ -5,31 +5,36 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Sandbox Agent</title>
|
||||
<!-- Preconnect to font providers -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #0a0a0a;
|
||||
--surface: #1c1c1e;
|
||||
--surface-2: #0f0f0f;
|
||||
--border: #1c1c1e;
|
||||
--border-2: #2c2c2e;
|
||||
--input-bg: #1c1c1e;
|
||||
--input-border: #2c2c2e;
|
||||
--text: #f5f5f7;
|
||||
--text-secondary: #e5e5e7;
|
||||
--muted: #6e6e73;
|
||||
--muted-2: #4a4a4c;
|
||||
/* Match landing page colors */
|
||||
--bg: #000000;
|
||||
--surface: rgba(255, 255, 255, 0.02);
|
||||
--surface-2: rgba(255, 255, 255, 0.01);
|
||||
--border: rgba(255, 255, 255, 0.1);
|
||||
--border-2: rgba(255, 255, 255, 0.1);
|
||||
--input-bg: rgba(255, 255, 255, 0.02);
|
||||
--input-border: rgba(255, 255, 255, 0.1);
|
||||
--text: #ffffff;
|
||||
--text-secondary: #a1a1aa; /* zinc-400 */
|
||||
--muted: #71717a; /* zinc-500 */
|
||||
--muted-2: #52525b; /* zinc-600 */
|
||||
--accent: #ff4f00;
|
||||
--accent-hover: #ff6a00;
|
||||
--success: #30d158;
|
||||
--warning: #ff9f0a;
|
||||
--danger: #ff3b30;
|
||||
--purple: #bf5af2;
|
||||
--cyan: #64d2ff;
|
||||
--success: #4ade80; /* green-400 */
|
||||
--warning: #fbbf24; /* amber-400 */
|
||||
--danger: #f87171; /* red-400 */
|
||||
--purple: #a78bfa; /* violet-400 */
|
||||
--cyan: #22d3ee; /* cyan-400 */
|
||||
--radius: 8px;
|
||||
--radius-sm: 6px;
|
||||
--radius-lg: 12px;
|
||||
--transition: 150ms ease;
|
||||
--radius-lg: 16px;
|
||||
--transition: 200ms ease;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -45,9 +50,22 @@
|
|||
|
||||
body {
|
||||
color: var(--text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", Roboto, sans-serif;
|
||||
font-family: 'Open Sans', system-ui, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Text selection - match landing page */
|
||||
::selection {
|
||||
background-color: rgba(255, 79, 0, 0.3);
|
||||
color: #fed7aa;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: rgba(255, 79, 0, 0.3);
|
||||
color: #fed7aa;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
@ -110,25 +128,6 @@
|
|||
color: var(--muted);
|
||||
}
|
||||
|
||||
.header-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--border-2);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition), border-color var(--transition);
|
||||
}
|
||||
|
||||
.header-link:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.status-indicator.disconnected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -198,9 +197,14 @@
|
|||
|
||||
.connect-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 24px;
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
|
||||
.connect-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.connect-card-title {
|
||||
|
|
@ -287,36 +291,45 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--radius);
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition), opacity var(--transition);
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button.primary:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
background: #e4e4e7;
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: var(--border-2);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.button.secondary:hover:not(:disabled) {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.button.ghost {
|
||||
background: transparent;
|
||||
color: var(--accent);
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.button.ghost:hover:not(:disabled) {
|
||||
background: rgba(255, 79, 0, 0.1);
|
||||
}
|
||||
|
||||
.button.danger {
|
||||
background: var(--danger);
|
||||
color: #fff;
|
||||
|
|
@ -324,12 +337,12 @@
|
|||
|
||||
.button.success {
|
||||
background: var(--success);
|
||||
color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.button.small {
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
|
|
@ -422,8 +435,8 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--surface-2);
|
||||
min-height: 0;
|
||||
background: var(--bg);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
|
|
@ -502,10 +515,10 @@
|
|||
top: 36px;
|
||||
left: 0;
|
||||
min-width: 220px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-2);
|
||||
background: #000000;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -574,20 +587,6 @@
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.setup-custom-back {
|
||||
flex-shrink: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--accent);
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.setup-custom-back:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.session-create-section {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -1024,8 +1023,8 @@
|
|||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px;
|
||||
border-radius: var(--radius);
|
||||
padding: 12px;
|
||||
margin-bottom: 4px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
|
|
@ -1038,12 +1037,12 @@
|
|||
|
||||
.session-item:hover {
|
||||
background: var(--surface);
|
||||
border-color: var(--border-2);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.session-item.active {
|
||||
background: rgba(255, 79, 0, 0.1);
|
||||
border-color: var(--accent);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.session-item-id {
|
||||
|
|
@ -1088,23 +1087,6 @@
|
|||
color: var(--danger);
|
||||
}
|
||||
|
||||
.session-persistence-note {
|
||||
padding: 8px 10px 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
font-size: 10px;
|
||||
line-height: 1.45;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.session-persistence-note a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.session-persistence-note a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Chat Panel */
|
||||
.chat-panel {
|
||||
display: flex;
|
||||
|
|
@ -1117,7 +1099,7 @@
|
|||
height: 41px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface-2);
|
||||
background: var(--bg);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -1218,6 +1200,26 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.mock-agent-hint {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-2);
|
||||
border-radius: var(--radius);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
max-width: 320px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.mock-agent-hint code {
|
||||
background: var(--border-2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.empty-state-menu-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -1272,23 +1274,24 @@
|
|||
}
|
||||
|
||||
.message-content {
|
||||
padding: 10px 14px;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius-lg);
|
||||
max-width: 80%;
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.message.user .message-content {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.message.assistant .message-content {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
|
@ -1326,6 +1329,106 @@
|
|||
color: var(--danger);
|
||||
}
|
||||
|
||||
.message.no-avatar {
|
||||
padding-left: 38px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
color: var(--danger);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.system-icon {
|
||||
color: var(--muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.status-divider-line {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.status-divider-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Collapsible Messages */
|
||||
.collapsible-message {
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.collapsible-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: color var(--transition);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.collapsible-header:hover {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.collapsible-message.tool .collapsible-header {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.collapsible-message.tool .collapsible-header:hover {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.collapsible-message.error .collapsible-header {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.collapsible-message.system .collapsible-header {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 4px;
|
||||
background: rgba(100, 210, 255, 0.15);
|
||||
color: var(--cyan);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
padding: 0 12px 12px 38px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -1372,64 +1475,6 @@
|
|||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.toast-stack {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: min(420px, calc(100vw - 24px));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid rgba(255, 59, 48, 0.35);
|
||||
background: rgba(28, 8, 8, 0.95);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.toast-content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.toast-title {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
color: var(--danger);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.toast-message {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.toast-close:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
width: 2px;
|
||||
|
|
@ -1505,14 +1550,14 @@
|
|||
display: flex;
|
||||
gap: 10px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 10px 12px;
|
||||
padding: 12px 14px;
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
|
||||
.input-wrapper:focus-within {
|
||||
border-color: var(--accent);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.input-wrapper textarea {
|
||||
|
|
@ -1589,7 +1634,7 @@
|
|||
|
||||
.session-config-value {
|
||||
font-size: 12px;
|
||||
color: #8e8e93;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Setup Row */
|
||||
|
|
@ -1800,7 +1845,7 @@
|
|||
display: flex;
|
||||
height: 41px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface-2);
|
||||
background: var(--bg);
|
||||
flex-shrink: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
|
@ -1808,14 +1853,13 @@
|
|||
.debug-tab {
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
cursor: pointer;
|
||||
transition: color var(--transition), border-color var(--transition);
|
||||
white-space: nowrap;
|
||||
|
|
@ -1873,10 +1917,15 @@
|
|||
/* Cards */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-2);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.card:last-child {
|
||||
|
|
@ -1967,13 +2016,18 @@
|
|||
|
||||
.event-item {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-2);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
|
||||
.event-item:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.event-item.expanded {
|
||||
box-shadow: 0 0 0 1px rgba(255, 79, 0, 0.12);
|
||||
border-color: rgba(255, 79, 0, 0.3);
|
||||
}
|
||||
|
||||
.event-summary {
|
||||
|
|
@ -2040,89 +2094,87 @@
|
|||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
/* ACP event categories: connection */
|
||||
.event-type.connection,
|
||||
.event-type.session {
|
||||
.event-type.session,
|
||||
.event-type.session-started,
|
||||
.event-type.session-ended {
|
||||
color: var(--success);
|
||||
}
|
||||
.event-icon.connection,
|
||||
.event-icon.session {
|
||||
|
||||
.event-type.item,
|
||||
.event-type.item-started,
|
||||
.event-type.item-completed {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.event-type.item-delta {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.event-type.error,
|
||||
.event-type.agent-unparsed {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.event-type.question,
|
||||
.event-type.question-requested,
|
||||
.event-type.question-resolved {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.event-type.permission,
|
||||
.event-type.permission-requested,
|
||||
.event-type.permission-resolved {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
.event-icon.session,
|
||||
.event-icon.session-started,
|
||||
.event-icon.session-ended {
|
||||
color: var(--success);
|
||||
border-color: rgba(48, 209, 88, 0.35);
|
||||
background: rgba(48, 209, 88, 0.12);
|
||||
}
|
||||
|
||||
/* ACP event categories: prompt / tool */
|
||||
.event-type.prompt,
|
||||
.event-type.tool {
|
||||
color: var(--accent);
|
||||
}
|
||||
.event-icon.prompt,
|
||||
.event-icon.tool {
|
||||
.event-icon.item,
|
||||
.event-icon.item-started,
|
||||
.event-icon.item-completed {
|
||||
color: var(--accent);
|
||||
border-color: rgba(255, 79, 0, 0.35);
|
||||
background: rgba(255, 79, 0, 0.12);
|
||||
}
|
||||
|
||||
/* ACP event categories: update / terminal (streaming, realtime) */
|
||||
.event-type.update,
|
||||
.event-type.terminal {
|
||||
color: var(--cyan);
|
||||
}
|
||||
.event-icon.update,
|
||||
.event-icon.terminal {
|
||||
.event-icon.item-delta {
|
||||
color: var(--cyan);
|
||||
border-color: rgba(100, 210, 255, 0.35);
|
||||
background: rgba(100, 210, 255, 0.12);
|
||||
}
|
||||
|
||||
/* ACP event categories: cancel */
|
||||
.event-type.cancel {
|
||||
color: var(--danger);
|
||||
}
|
||||
.event-icon.cancel {
|
||||
.event-icon.error,
|
||||
.event-icon.agent-unparsed {
|
||||
color: var(--danger);
|
||||
border-color: rgba(255, 59, 48, 0.35);
|
||||
background: rgba(255, 59, 48, 0.12);
|
||||
}
|
||||
|
||||
/* ACP event categories: filesystem */
|
||||
.event-type.filesystem {
|
||||
color: var(--warning);
|
||||
}
|
||||
.event-icon.filesystem {
|
||||
.event-icon.question,
|
||||
.event-icon.question-requested,
|
||||
.event-icon.question-resolved {
|
||||
color: var(--warning);
|
||||
border-color: rgba(255, 159, 10, 0.35);
|
||||
background: rgba(255, 159, 10, 0.12);
|
||||
}
|
||||
|
||||
/* ACP event categories: config / permission */
|
||||
.event-type.config,
|
||||
.event-type.permission {
|
||||
color: var(--purple);
|
||||
}
|
||||
.event-icon.config,
|
||||
.event-icon.permission {
|
||||
.event-icon.permission,
|
||||
.event-icon.permission-requested,
|
||||
.event-icon.permission-resolved {
|
||||
color: var(--purple);
|
||||
border-color: rgba(191, 90, 242, 0.35);
|
||||
background: rgba(191, 90, 242, 0.12);
|
||||
}
|
||||
|
||||
/* ACP event categories: response (fallback) */
|
||||
.event-type.response {
|
||||
color: var(--muted);
|
||||
}
|
||||
.event-icon.response {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 10px;
|
||||
color: var(--muted);
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border-2);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.event-id {
|
||||
|
|
@ -2175,7 +2227,7 @@
|
|||
.log-method {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.log-url {
|
||||
|
|
@ -2283,8 +2335,8 @@
|
|||
}
|
||||
|
||||
.feature-coverage-badge.enabled {
|
||||
background: rgba(48, 209, 88, 0.12);
|
||||
color: var(--success);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.feature-coverage-badge.disabled {
|
||||
|
|
@ -2296,26 +2348,32 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.messages-container::-webkit-scrollbar,
|
||||
.debug-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
/* Scrollbar - match landing page */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #3f3f46 transparent;
|
||||
}
|
||||
|
||||
.messages-container::-webkit-scrollbar-track,
|
||||
.debug-content::-webkit-scrollbar-track {
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.messages-container::-webkit-scrollbar-thumb,
|
||||
.debug-content::-webkit-scrollbar-thumb {
|
||||
background: #3a3a3c;
|
||||
border-radius: 3px;
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #3f3f46;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.messages-container::-webkit-scrollbar-thumb:hover,
|
||||
.debug-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #48484a;
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #52525b;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
|
|
@ -2353,13 +2411,6 @@
|
|||
.header-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toast-stack {
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,102 +1,150 @@
|
|||
import { useState } from "react";
|
||||
import { getAvatarLabel, getMessageClass } from "./messageUtils";
|
||||
import renderContentPart from "./renderContentPart";
|
||||
import type { TimelineEntry } from "./types";
|
||||
import { formatJson } from "../../utils/format";
|
||||
import { AlertTriangle, Settings, ChevronRight, ChevronDown } from "lucide-react";
|
||||
|
||||
const CollapsibleMessage = ({
|
||||
id,
|
||||
icon,
|
||||
label,
|
||||
children,
|
||||
className = ""
|
||||
}: {
|
||||
id: string;
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={`collapsible-message ${className}`}>
|
||||
<button className="collapsible-header" onClick={() => setExpanded(!expanded)}>
|
||||
{expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
||||
{icon}
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
{expanded && <div className="collapsible-content">{children}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatMessages = ({
|
||||
entries,
|
||||
sessionError,
|
||||
eventError,
|
||||
messagesEndRef
|
||||
}: {
|
||||
entries: TimelineEntry[];
|
||||
sessionError: string | null;
|
||||
eventError: string | null;
|
||||
messagesEndRef: React.RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
return (
|
||||
<div className="messages">
|
||||
{entries.map((entry) => {
|
||||
const messageClass = getMessageClass(entry);
|
||||
|
||||
if (entry.kind === "meta") {
|
||||
return (
|
||||
<div key={entry.id} className={`message ${messageClass}`}>
|
||||
<div className="avatar">{getAvatarLabel(messageClass)}</div>
|
||||
<div className="message-content">
|
||||
<div className="message-meta">
|
||||
<span>{entry.meta?.title ?? "Status"}</span>
|
||||
</div>
|
||||
{entry.meta?.detail && <div className="part-body">{entry.meta.detail}</div>}
|
||||
const isError = entry.meta?.severity === "error";
|
||||
const title = entry.meta?.title ?? "Status";
|
||||
const isStatusDivider = ["Session Started", "Turn Started", "Turn Ended"].includes(title);
|
||||
|
||||
if (isStatusDivider) {
|
||||
return (
|
||||
<div key={entry.id} className="status-divider">
|
||||
<div className="status-divider-line" />
|
||||
<span className="status-divider-text">
|
||||
<Settings size={12} />
|
||||
{title}
|
||||
</span>
|
||||
<div className="status-divider-line" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Other status messages - collapsible
|
||||
return (
|
||||
<CollapsibleMessage
|
||||
key={entry.id}
|
||||
id={entry.id}
|
||||
icon={isError ? <AlertTriangle size={14} className="error-icon" /> : <Settings size={14} className="system-icon" />}
|
||||
label={title}
|
||||
className={isError ? "error" : "system"}
|
||||
>
|
||||
{entry.meta?.detail && <div className="part-body">{entry.meta.detail}</div>}
|
||||
</CollapsibleMessage>
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.kind === "reasoning") {
|
||||
const item = entry.item;
|
||||
if (!item) return null;
|
||||
const hasParts = (item.content ?? []).length > 0;
|
||||
const isInProgress = item.status === "in_progress";
|
||||
const isFailed = item.status === "failed";
|
||||
const messageClass = getMessageClass(item);
|
||||
const statusLabel = item.status !== "completed" ? item.status.replace("_", " ") : "";
|
||||
const kindLabel = item.kind.replace("_", " ");
|
||||
const isTool = messageClass === "tool";
|
||||
|
||||
// Tool results - collapsible
|
||||
if (isTool) {
|
||||
return (
|
||||
<div key={entry.id} className="message assistant">
|
||||
<div className="avatar">AI</div>
|
||||
<div className="message-content">
|
||||
<div className="message-meta">
|
||||
<span>reasoning - {entry.reasoning?.visibility ?? "public"}</span>
|
||||
</div>
|
||||
<div className="part-body muted">{entry.reasoning?.text ?? ""}</div>
|
||||
</div>
|
||||
</div>
|
||||
<CollapsibleMessage
|
||||
key={entry.id}
|
||||
id={entry.id}
|
||||
icon={<span className="tool-icon">T</span>}
|
||||
label={`${kindLabel}${statusLabel ? ` (${statusLabel})` : ""}`}
|
||||
className="tool"
|
||||
>
|
||||
{hasParts ? (
|
||||
(item.content ?? []).map(renderContentPart)
|
||||
) : entry.deltaText ? (
|
||||
<span>{entry.deltaText}</span>
|
||||
) : (
|
||||
<span className="muted">No content.</span>
|
||||
)}
|
||||
</CollapsibleMessage>
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.kind === "tool") {
|
||||
const isComplete = entry.toolStatus === "completed" || entry.toolStatus === "failed";
|
||||
const isFailed = entry.toolStatus === "failed";
|
||||
return (
|
||||
<div key={entry.id} className={`message tool ${isFailed ? "error" : ""}`}>
|
||||
<div className="avatar">{getAvatarLabel(isFailed ? "error" : "tool")}</div>
|
||||
<div className="message-content">
|
||||
return (
|
||||
<div key={entry.id} className={`message ${messageClass} ${isFailed ? "error no-avatar" : ""}`}>
|
||||
{!isFailed && <div className="avatar">{getAvatarLabel(messageClass)}</div>}
|
||||
<div className="message-content">
|
||||
{(item.kind !== "message" || item.status !== "completed") && (
|
||||
<div className="message-meta">
|
||||
<span>tool call - {entry.toolName}</span>
|
||||
{entry.toolStatus && entry.toolStatus !== "completed" && (
|
||||
<span className={`pill ${isFailed ? "danger" : "accent"}`}>
|
||||
{entry.toolStatus.replace("_", " ")}
|
||||
{isFailed && <AlertTriangle size={14} className="error-icon" />}
|
||||
<span>{kindLabel}</span>
|
||||
{statusLabel && (
|
||||
<span className={`pill ${item.status === "failed" ? "danger" : "accent"}`}>
|
||||
{statusLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{entry.toolInput && <pre className="code-block">{entry.toolInput}</pre>}
|
||||
{isComplete && entry.toolOutput && (
|
||||
<div className="part">
|
||||
<div className="part-title">result</div>
|
||||
<pre className="code-block">{entry.toolOutput}</pre>
|
||||
</div>
|
||||
)}
|
||||
{!isComplete && !entry.toolInput && (
|
||||
<span className="thinking-indicator">
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Message (user or assistant)
|
||||
return (
|
||||
<div key={entry.id} className={`message ${messageClass}`}>
|
||||
<div className="avatar">{getAvatarLabel(messageClass)}</div>
|
||||
<div className="message-content">
|
||||
{entry.text ? (
|
||||
<div className="part-body">{entry.text}</div>
|
||||
) : (
|
||||
)}
|
||||
{hasParts ? (
|
||||
(item.content ?? []).map(renderContentPart)
|
||||
) : entry.deltaText ? (
|
||||
<span>
|
||||
{entry.deltaText}
|
||||
{isInProgress && <span className="cursor" />}
|
||||
</span>
|
||||
) : isInProgress ? (
|
||||
<span className="thinking-indicator">
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="muted">No content yet.</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{sessionError && <div className="message-error">{sessionError}</div>}
|
||||
{eventError && <div className="message-error">{eventError}</div>}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import type { TimelineEntry } from "./types";
|
||||
|
||||
export const getMessageClass = (entry: TimelineEntry) => {
|
||||
if (entry.kind === "tool") return "tool";
|
||||
if (entry.kind === "meta") return entry.meta?.severity === "error" ? "error" : "system";
|
||||
if (entry.kind === "reasoning") return "assistant";
|
||||
if (entry.role === "user") return "user";
|
||||
return "assistant";
|
||||
};
|
||||
|
||||
export const getAvatarLabel = (messageClass: string) => {
|
||||
if (messageClass === "user") return "U";
|
||||
if (messageClass === "tool") return "T";
|
||||
if (messageClass === "system") return "S";
|
||||
if (messageClass === "error") return "!";
|
||||
return "AI";
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import type { UniversalItem } from "sandbox-agent";
|
||||
import { Settings, AlertTriangle, User } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const getMessageClass = (item: UniversalItem) => {
|
||||
if (item.kind === "tool_call" || item.kind === "tool_result") return "tool";
|
||||
if (item.kind === "system" || item.kind === "status") return "system";
|
||||
if (item.role === "user") return "user";
|
||||
if (item.role === "tool") return "tool";
|
||||
if (item.role === "system") return "system";
|
||||
return "assistant";
|
||||
};
|
||||
|
||||
export const getAvatarLabel = (messageClass: string): ReactNode => {
|
||||
if (messageClass === "user") return <User size={14} />;
|
||||
if (messageClass === "tool") return "T";
|
||||
if (messageClass === "system") return <Settings size={14} />;
|
||||
if (messageClass === "error") return <AlertTriangle size={14} />;
|
||||
return "AI";
|
||||
};
|
||||
|
|
@ -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()
|
||||
]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
Claude Code, Codex, OpenCode, Amp, and Pi each have different APIs. We provide a single,
|
||||
unified interface to control them all.
|
||||
</p>
|
||||
<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, and Amp each have different APIs. We provide a single,
|
||||
unified interface to control them all.
|
||||
</p>
|
||||
</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>
|
||||
<h4 className="text-base font-normal text-white">Streaming Events</h4>
|
||||
</div>
|
||||
<p className="text-zinc-400 text-sm leading-relaxed">
|
||||
<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>
|
||||
<h4 className="text-base font-normal 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. Approve or deny tool executions remotely over HTTP.
|
||||
<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>
|
||||
<h4 className="text-base font-normal text-white">Runs Inside Any Sandbox</h4>
|
||||
</div>
|
||||
<p className="text-zinc-400 text-sm leading-relaxed">
|
||||
<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>
|
||||
<h4 className="text-base font-normal text-white">Session Management</h4>
|
||||
</div>
|
||||
<p className="text-zinc-400 text-sm leading-relaxed">
|
||||
<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>
|
||||
<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-400 text-sm leading-relaxed">
|
||||
<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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
© {new Date().getFullYear()} Rivet Gaming, Inc. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Terminal, Check, ArrowRight } from 'lucide-react';
|
||||
|
||||
const AGENT_PROCESSES = [
|
||||
{ label: 'Claude Code', color: '#D97757', x: 35, y: 70, logo: '/logos/claude.svg' },
|
||||
{ label: 'Codex', color: '#10A37F', x: 185, y: 70, logo: 'openai' },
|
||||
{ label: 'Amp', color: '#F59E0B', x: 35, y: 155, logo: '/logos/amp.svg' },
|
||||
{ label: 'OpenCode', color: '#8B5CF6', x: 185, y: 155, logo: 'opencode' },
|
||||
const ADAPTERS = [
|
||||
{ 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() {
|
||||
|
|
@ -15,29 +17,22 @@ function UniversalAPIDiagram() {
|
|||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setActiveIndex((prev) => (prev + 1) % AGENT_PROCESSES.length);
|
||||
setActiveIndex((prev) => (prev + 1) % ADAPTERS.length);
|
||||
}, 2000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
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: AGENT_PROCESSES[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%">
|
||||
|
|
@ -49,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>
|
||||
|
|
@ -73,55 +69,60 @@ 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
|
||||
</text>
|
||||
{/* 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
|
||||
</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">
|
||||
{/* SANDBOX AGENT SDK */}
|
||||
<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 AGENT PROCESSES */}
|
||||
{AGENT_PROCESSES.map((p, i) => {
|
||||
{/* PROVIDER ADAPTERS */}
|
||||
{ADAPTERS.map((p, i) => {
|
||||
const isActive = i === activeIndex;
|
||||
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">
|
||||
<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.3685v 2.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.4998v 2.9994l-2.5974 1.4997-2.6067-1.4997Z" fill="#ffffff" />
|
||||
<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}
|
||||
>
|
||||
|
|
@ -133,19 +134,18 @@ function UniversalAPIDiagram() {
|
|||
|
||||
{/* Active Agent Label */}
|
||||
<text
|
||||
x="165"
|
||||
x="180"
|
||||
y="250"
|
||||
fill={AGENT_PROCESSES[activeIndex].color}
|
||||
fill={ADAPTERS[activeIndex].color}
|
||||
textAnchor="middle"
|
||||
fontSize="10"
|
||||
fontSize="12"
|
||||
fontWeight="800"
|
||||
fontFamily="monospace"
|
||||
letterSpacing="0.1em"
|
||||
>
|
||||
CONNECTED TO {AGENT_PROCESSES[activeIndex].label.toUpperCase()}
|
||||
CONNECTED TO {ADAPTERS[activeIndex].label.toUpperCase()}
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -166,47 +166,104 @@ const CopyInstallButton = () => {
|
|||
};
|
||||
|
||||
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>
|
||||
<div className="relative group w-full sm:w-auto">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
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 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() {
|
||||
const [scrollOpacity, setScrollOpacity] = useState(1);
|
||||
|
||||
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 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, or Amp — streaming events, handling permissions, managing sessions.
|
||||
</p>
|
||||
<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" />
|
||||
|
||||
<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'
|
||||
{/* 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"
|
||||
>
|
||||
Read the Docs
|
||||
<ArrowRight className='h-4 w-4' />
|
||||
</a>
|
||||
<CopyInstallButton />
|
||||
</div>
|
||||
</div>
|
||||
Run Coding Agents in Sandboxes.
|
||||
<br />
|
||||
<span className="text-zinc-400">Control Them Over HTTP.</span>
|
||||
</motion.h1>
|
||||
|
||||
<div className="flex-1 w-full max-w-2xl">
|
||||
<UniversalAPIDiagram />
|
||||
<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="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" />
|
||||
</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>
|
||||
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
Built-in Debugger
|
||||
</h2>
|
||||
<p className="mb-12 text-lg text-zinc-400">
|
||||
Inspect sessions, view event payloads, and troubleshoot without writing code.
|
||||
</p>
|
||||
<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
|
||||
</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 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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
{children}
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href={href}
|
||||
className="px-3 py-2 text-sm font-medium text-zinc-400 transition-colors duration-200 hover:text-white"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
<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" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-red-400">Problem</span>
|
||||
</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="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" />
|
||||
</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>
|
||||
</div>
|
||||
{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">
|
||||
<X className="h-3 w-3 text-zinc-600" />
|
||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-600">Problem</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed text-zinc-500">
|
||||
{friction.problem}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-auto border-t border-white/5 pt-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Check className="h-3 w-3 text-green-400" />
|
||||
<span className="text-[10px] font-medium uppercase tracking-wider text-zinc-400">Solution</span>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed text-zinc-300">
|
||||
{friction.solution}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ 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 />
|
||||
<Navigation client:load />
|
||||
<main>
|
||||
<Hero client:load />
|
||||
<PainPoints client:visible />
|
||||
|
|
@ -21,6 +20,5 @@ import { Footer } from '../components/Footer';
|
|||
<Inspector client:visible />
|
||||
<FAQ client:visible />
|
||||
</main>
|
||||
<Footer client:visible />
|
||||
</div>
|
||||
<Footer client:visible />
|
||||
</Layout>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue