diff --git a/website/.astro/content-modules.mjs b/website/.astro/content-modules.mjs index 0efe088f7..f29c754ec 100644 --- a/website/.astro/content-modules.mjs +++ b/website/.astro/content-modules.mjs @@ -1,15 +1,15 @@ export default new Map([ -["src/content/docs/docs/agent-to-agent.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagent-to-agent.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/authentication.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fauthentication.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/benchmarks.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fbenchmarks.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/configuration.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fconfiguration.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/core.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcore.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/crash-course.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcrash-course.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/cron.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcron.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/agent-to-agent.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagent-to-agent.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/benchmarks.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fbenchmarks.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/deployment.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fdeployment.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/cron.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcron.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/crash-course.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcrash-course.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/events.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fevents.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/filesystem.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Ffilesystem.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/core.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcore.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/index.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Findex.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/limitations.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Flimitations.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/llm-credentials.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fllm-credentials.mdx&astroContentModuleFlag=true")], @@ -18,23 +18,23 @@ export default new Map([ ["src/content/docs/docs/networking.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fnetworking.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/permissions.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fpermissions.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/persistence.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fpersistence.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/processes.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fprocesses.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/queues.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fqueues.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/processes.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fprocesses.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/quickstart.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fquickstart.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/sandbox.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsandbox.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/security-model.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsecurity-model.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/security.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsecurity.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/sessions.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsessions.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/software.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsoftware.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/sqlite.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsqlite.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/system-prompt.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsystem-prompt.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/tools.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Ftools.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/sessions.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsessions.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/versus-sandbox.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fversus-sandbox.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/webhooks.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fwebhooks.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/workflows.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fworkflows.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/agents/amp.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Famp.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/agents/claude.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fclaude.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/agents/codex.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fcodex.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/agents/opencode.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fopencode.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/agents/pi.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fpi.mdx&astroContentModuleFlag=true")]]); +["src/content/docs/docs/agents/codex.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fcodex.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/agents/pi.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fpi.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/agents/amp.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Famp.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/agents/claude.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fclaude.mdx&astroContentModuleFlag=true")]]); \ No newline at end of file diff --git a/website/astro.config.mjs b/website/astro.config.mjs index b4772a49e..8b8f3d93c 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -18,7 +18,7 @@ export default defineConfig({ tailwind({ applyBaseStyles: false }), // The shared Rivet docs theme — wraps Starlight entirely. All docs // branding/chrome lives in @rivet-dev/docs-theme; docs.config.mjs maps - // Agent OS's identity, nav, and pages onto it. + // agentOS's identity, nav, and pages onto it. ...docsTheme(starlight, siteConfig), sitemap(), ], diff --git a/website/docs.config.mjs b/website/docs.config.mjs index a65753cc9..57ffa43f8 100644 --- a/website/docs.config.mjs +++ b/website/docs.config.mjs @@ -1,5 +1,5 @@ /** - * Agent OS docs configuration — the only non-content surface consumed by + * agentOS docs configuration — the only non-content surface consumed by * @rivet-dev/docs-theme. Everything visual (theme, header chrome, sidebar * icons, code blocks) lives in the package; this file maps agentOS's product * identity, navigation, and pages onto it. @@ -11,7 +11,7 @@ * @type {import('@rivet-dev/docs-theme').SiteConfig} */ export const siteConfig = { - product: "Agent OS", + product: "agentOS", productLogo: "/images/agent-os/agentos-hero-logo.svg", productHome: "/", favicon: "/favicon.svg", diff --git a/website/public/favicon.svg b/website/public/favicon.svg index d7119e885..33655f670 100644 --- a/website/public/favicon.svg +++ b/website/public/favicon.svg @@ -1,8 +1,30 @@ - - - - - - - + + + + + + + + + + + + + + + + + + OS + + + + diff --git a/website/src/components/Footer.tsx b/website/src/components/Footer.tsx index 3f48ff37c..cf3dff338 100644 --- a/website/src/components/Footer.tsx +++ b/website/src/components/Footer.tsx @@ -1,7 +1,6 @@ "use client"; import { motion } from "framer-motion"; -import { MessageCircle } from "lucide-react"; const footer = { product: [ @@ -14,16 +13,15 @@ const footer = { { name: "Changelog", href: "https://github.com/rivet-dev/agent-os/releases" }, { name: "GitHub", href: "https://github.com/rivet-dev/agent-os" }, ], - legal: [ - { name: "Terms", href: "https://rivet.dev/terms" }, - { name: "Privacy Policy", href: "https://rivet.dev/privacy" }, - { name: "Acceptable Use", href: "https://rivet.dev/acceptable-use" }, - ], social: [ { name: "Discord", href: "https://rivet.dev/discord", - icon: , + icon: ( + + + + ), }, { name: "GitHub", @@ -59,7 +57,7 @@ export function Footer() { className="space-y-6 xl:col-span-4" > - Agent OS + agentOS

A portable open-source operating system for agents.

@@ -78,7 +76,7 @@ export function Footer() {
-
+

Product

    @@ -104,19 +102,6 @@ export function Footer() { ))}
- - -

Legal

- -
@@ -127,7 +112,7 @@ export function Footer() { transition={{ duration: 0.5, delay: 0.3 }} className="mt-12 border-t border-ink/10 pt-8" > -

© {new Date().getFullYear()} Agent OS. Apache 2.0 licensed.

+

© {new Date().getFullYear()} agentOS. Apache 2.0 licensed.

diff --git a/website/src/components/Navigation.tsx b/website/src/components/Navigation.tsx index e9ea9f03c..318fdc6b0 100644 --- a/website/src/components/Navigation.tsx +++ b/website/src/components/Navigation.tsx @@ -1,23 +1,47 @@ "use client"; import { useState, useEffect } from "react"; -import { Menu, X, MessageCircle } from "lucide-react"; +import { Menu, X } from "lucide-react"; import { GitHubStars } from "./GitHubStars"; +import { registry } from "../data/registry"; -const NAV_LINKS = [ +function DiscordIcon({ className }: { className?: string }) { + return ( + + ); +} + +const NAV_LINKS: { href: string; label: string; badge?: number }[] = [ { href: "/use-cases", label: "Use Cases" }, { href: "/pricing", label: "Pricing" }, - { href: "/registry", label: "Registry" }, + { href: "/registry", label: "Registry", badge: registry.length }, { href: "/docs", label: "Docs" }, ]; -function NavItem({ href, children }: { href: string; children: React.ReactNode }) { +function NavBadge({ count }: { count: number }) { + return ( + + {count} + + ); +} + +function NavItem({ href, children, badge }: { href: string; children: React.ReactNode; badge?: number }) { return ( {children} + {badge != null && } ); } @@ -53,15 +77,15 @@ export function Navigation() {
Agent OS
{NAV_LINKS.map((link) => ( - + {link.label} ))} @@ -74,7 +98,7 @@ export function Navigation() { className="inline-flex h-10 items-center justify-center whitespace-nowrap rounded-md border border-ink/15 px-4 py-2 text-sm text-ink-soft transition-colors hover:border-ink/30 hover:text-ink" aria-label="Discord" > - + setMobileMenuOpen(false)} > {link.label} + {link.badge != null && } ))}
@@ -113,7 +138,7 @@ export function Navigation() { onClick={() => setMobileMenuOpen(false)} aria-label="Discord" > - + Discord green + mini bar). +// agentOS — every agent is packed into ONE shared process: a single box +// that boots once. +// The contrast (four separate boots vs one grouped boot) is the point. The +// container boot is shown slowed ~6x so the wait is visible. Numbers come from +// bench.ts so they stay accurate. +// --------------------------------------------------------------------------- + +const AGENTOS_MARK = '/images/agent-os/agentos-logo-ink.svg'; +const AGENT_LOGOS = [ + '/images/agent-logos/pi.svg', + '/images/agent-logos/claude-code.svg', + '/images/agent-logos/codex.svg', + '/images/agent-logos/opencode.svg', + '/images/agent-logos/amp.svg', +]; +const agentAt = (i: number) => AGENT_LOGOS[(i * 7) % AGENT_LOGOS.length]; + +const RED = '#d6453a'; +const GREEN = '#3f9a59'; +const BORDER_RED = 'rgba(214,69,58,0.6)'; +const BORDER_GREEN = 'rgba(63,154,89,0.65)'; + +// ---- Animation timing ------------------------------------------------------ +// The boot is scaled to each percentile's REAL container cold start, always +// slowed 6x — so p99 crawls ~6x longer than p50, in true proportion. +const SLOWDOWN = 6; +// agentOS boots near-instantly, so its box snaps done in a fixed, snappy time — +// independent of the container's 6x slowdown. It must never appear to "wait". +const AOS_BOOT_SEC = 0.5; +const bootDuration = (containerMs: number) => (containerMs / 1000) * SLOWDOWN; +const slowdownFactor = (containerMs: number) => (bootDuration(containerMs) * 1000) / containerMs; +const slowdownLabel = (containerMs: number) => { + const factor = slowdownFactor(containerMs); + return factor < 1.4 ? 'in real time' : `slowed ~${Math.round(factor)}×`; +}; +// Pill copy for the containers row. Phrased as a playback note so it reads as a +// display speed, not a perf claim that would clash with agentOS's "Nx faster". +const slowdownPill = (containerMs: number) => { + const factor = slowdownFactor(containerMs); + return factor < 1.4 ? 'shown in real time' : `shown ~${Math.round(factor)}× slower`; +}; + +const MiniBar = ({ width, color }: { width: MotionValue; color: MotionValue }) => ( +
+ +
+); + +// One container: its own agent, booting on its own. +const ContainerBox = ({ progress, lo, hi, logo }: { progress: MotionValue; lo: number; hi: number; logo: string }) => { + const border = useTransform(progress, [lo, hi], [BORDER_RED, BORDER_GREEN]); + const barWidth = useTransform(progress, [lo, hi], ['14%', '100%']); + const barColor = useTransform(progress, [lo, hi], [RED, GREEN]); + return ( +
+ + + + +
+ ); +}; + +// agentOS: all agents packed inside ONE process box that boots once. +const SharedProcessBox = ({ progress, count, doneAt }: { progress: MotionValue; count: number; doneAt: number }) => { + const border = useTransform(progress, [0, doneAt], [BORDER_RED, BORDER_GREEN]); + const barWidth = useTransform(progress, [0, doneAt], ['14%', '100%']); + const barColor = useTransform(progress, [0, doneAt], [RED, GREEN]); + return ( +
+ +
+ {Array.from({ length: count }).map((_, i) => ( + + + + ))} +
+
+ +
+ ); +}; + +type HostCfg = { + name: ReactNode; + finalMs: number; + doneAt: number; + units: number; + grouped: boolean; // agentOS packs all agents into one process box + accent: boolean; + badge?: ReactNode; +}; + +const Host = ({ cfg, progress }: { cfg: HostCfg; progress: MotionValue }) => { + const counter = useTransform(progress, (p) => `~${Math.round(Math.min(1, p / cfg.doneAt) * cfg.finalMs)} ms`); + const checkOpacity = useTransform(progress, [0, cfg.doneAt * 0.95, cfg.doneAt], [0, 0, 1]); + return ( +
+
+ {cfg.name} +
+ {cfg.badge} + + {counter} +
+
+ {cfg.grouped ? ( + + ) : ( +
+ {Array.from({ length: cfg.units }).map((_, i) => { + const lo = (i / cfg.units) * 0.08; + return ; + })} +
+ )} +
+ ); +}; + +export const ColdStartRace = () => { + const reduced = useReducedMotion(); + const ref = useRef(null); + const inView = useInView(ref, { once: true, margin: '-15% 0px' }); + const progress = useMotionValue(0); // containers — slowed boot + const aosProgress = useMotionValue(0); // agentOS — fixed snappy boot + const [pct, setPct] = useState(0); // p50 by default + + const cold = benchColdStart[pct]; + const aosMs = Math.round(cold.agentOS); + const containerMs = cold.sandbox; + const speedup = Math.round(cold.sandbox / cold.agentOS); + const durationSec = bootDuration(containerMs); + + // Play the boot once it scrolls into view; replay when the percentile changes. + // The containers crawl over the slowed duration; agentOS snaps done in a fixed + // fast time so it never appears to scale with the container slowdown. + useEffect(() => { + if (reduced) { + progress.set(1); + aosProgress.set(1); + return; + } + if (!inView) return; + progress.set(0); + aosProgress.set(0); + const c1 = animate(progress, [0, 1], { duration: durationSec, ease: 'easeInOut' }); + const c2 = animate(aosProgress, [0, 1], { duration: AOS_BOOT_SEC, ease: 'easeOut' }); + return () => { + c1.stop(); + c2.stop(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inView, reduced, pct]); + + return ( + +
+
+
+ Cold start + {slowdownPill(containerMs)} +
+
+ g.label)} active={pct} onChange={setPct} /> +
+
+ +
+ +
+ +

+ Each container is its own process that boots on its own; agentOS packs every agent into one shared process that boots once. Real {cold.label} cold start: ~{aosMs} ms vs ~{containerMs.toLocaleString()} ms ({speedup}× faster) — container boot shown {slowdownLabel(containerMs)}. +

+
+
+ ); +}; diff --git a/website/src/components/marketing/diagrams/ExecutionDensity.tsx b/website/src/components/marketing/diagrams/ExecutionDensity.tsx new file mode 100644 index 000000000..c53d4e605 --- /dev/null +++ b/website/src/components/marketing/diagrams/ExecutionDensity.tsx @@ -0,0 +1,222 @@ +'use client'; + +import { motion, useReducedMotion } from 'framer-motion'; +import { useEffect, useRef, useState } from 'react'; +import type { ReactNode } from 'react'; +import { Server, Container, SquareTerminal } from 'lucide-react'; +import { EASE, VIEWPORT, Reveal } from '../motion'; +import { BenchToggle, CountUpStat, BenchInfoTooltip } from './benchUI'; +import { benchWorkloads, SANDBOX_COST_PROVIDER, BENCHMARK_DATE, type WorkloadKey } from '../../../data/bench'; + +// --------------------------------------------------------------------------- +// Cost-per-execution-second, told as a density story. One server packs N +// concurrent executions (N = executions that fit per server at 70% utilization, +// from bench.ts) while a sandbox holds exactly one. The packed count is the +// denominator of the cost, so the packing animation resolves into the price: +// one $X/hr server / N executions = $Y per execution-second. +// Laid out as a full-width horizontal card. Driven by the shared workload toggle +// plus a local hardware-tier toggle. +// --------------------------------------------------------------------------- + +const WORKLOAD_KEYS = Object.keys(benchWorkloads) as WorkloadKey[]; +const AGENT_LOGOS = [ + '/images/agent-logos/pi.svg', + '/images/agent-logos/claude-code.svg', + '/images/agent-logos/codex.svg', + '/images/agent-logos/opencode.svg', + '/images/agent-logos/amp.svg', +]; +const CHIP_CAP = 48; // cap rendered chips; the rest fold into a "+N more" pill + +// Smoothly animates its own height to match its content. A ResizeObserver tracks +// the inner element's natural height (unaffected by child transforms), so when +// the packed-chip count changes the card grows/shrinks instead of jumping. The +// inner element keeps its real size — nothing is scaled — so no text distortion. +function AutoHeight({ children }: { children: ReactNode }) { + const reduced = useReducedMotion(); + const innerRef = useRef(null); + const [height, setHeight] = useState('auto'); + + useEffect(() => { + const el = innerRef.current; + if (!el) return; + const update = () => setHeight(el.offsetHeight); + update(); + const ro = new ResizeObserver(update); + ro.observe(el); + return () => ro.disconnect(); + }, []); + + return ( + +
{children}
+
+ ); +} + +export const ExecutionDensity = ({ workload, onWorkloadChange }: { workload: WorkloadKey; onWorkloadChange: (w: WorkloadKey) => void }) => { + const reduced = useReducedMotion(); + const [tierIdx, setTierIdx] = useState(0); // AWS ARM default + const [inView, setInView] = useState(false); + + const wl = benchWorkloads[workload]; + const tier = wl.cost[tierIdx]; + const execs = tier.execs; + const [mult, verb] = tier.multiplier.split(' '); // ['171x', 'cheaper'] + const shown = Math.min(execs, CHIP_CAP); + const overflow = execs - shown; + const activeIdx = WORKLOAD_KEYS.indexOf(workload); + + if (overflow > 0 && import.meta.env.DEV) { + console.info(`[ExecutionDensity] ${execs} execs (${workload}/${tier.label}); rendering ${shown} chips +${overflow} more (cap=${CHIP_CAP}).`); + } + + const chipGlyph = (i: number) => + workload === 'agent' ? ( + + ) : ( +