From 8bfacfed1e201a12580a6d6fbbde6139dc282115 Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 15 Sep 2026 04:13:26 +0000 Subject: [PATCH 1/2] Add Solutions section, BYOC deployment cards, mobile stack snap - New "Built for every kind of agent" section on the homepage with five solution cards (coding agents, agent app builders, company-specific agents, personal agents, realtime apps), each with a CSS-only hover vignette animation (hover-capable pointers, reduced-motion respected). - Deployment options: "Local & Self-Host", "Rivet Cloud", and a new "BYOC" card; BYOC added to pricing comparison, enterprise page, on-prem pillar, and footer. Docs untouched. - Agent stack section: scroll-snap cards on phones so each card fits between the sticky diagram and the bottom of the screen. - Dev server: pre-bundle heavy deps via vite.optimizeDeps (dev only). Amp-Thread-ID: https://ampcode.com/threads/T-01a0a1bf-48c6-77f8-b65d-0b681d9f18a9 Co-authored-by: Nicholas Kissel --- astro.config.ts | 16 + src/components/Footer.jsx | 1 + .../marketing/DeploymentOptions.tsx | 24 +- .../marketing/diagrams/deploymentDiagrams.tsx | 44 +- .../marketing/pricing/PricingPageClient.tsx | 66 +- .../marketing/sections/AgentStackSection.tsx | 23 +- .../marketing/sections/OnPremSection.tsx | 6 +- .../sections/SolutionsSection.module.css | 422 +++++++++++++ .../marketing/sections/SolutionsSection.tsx | 582 ++++++++++++++++++ src/pages/enterprise.astro | 56 +- src/pages/index.astro | 3 + 11 files changed, 1179 insertions(+), 64 deletions(-) create mode 100644 src/components/marketing/sections/SolutionsSection.module.css create mode 100644 src/components/marketing/sections/SolutionsSection.tsx diff --git a/astro.config.ts b/astro.config.ts index 19ab4724..aa9691be 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -93,8 +93,24 @@ export default defineConfig({ // discovered mid-hydration, the re-optimize invalidates the in-flight // import chain, and every large island on the page silently fails to // hydrate — docs sidebar dropdowns dead until a lucky reload. + // + // `@rivet-gg/components` is a linked workspace package, which Vite serves + // as raw source in dev: its barrel `src/index.ts` fans out to ~130 + // separate module requests on every page that imports even `cn`, and on + // a high-latency link that waterfall dominates page load. Listing it + // here pre-bundles it into a handful of chunks like any other dep. The + // cost is that edits inside packages/components need a dev-server + // restart (or `astro dev --force`) instead of HMR. Production builds are + // unaffected: optimizeDeps only applies to the dev server. Subpath + // entries are separate optimizer entries, so list each one the site + // imports; those resolve to `.tsx` files, which Vite refuses to treat + // as optimizable entries unless the extension is listed. optimizeDeps: { + extensions: ['.tsx'], include: [ + '@rivet-gg/components', + '@rivet-gg/components/header', + '@rivet-gg/components/mdx', 'mermaid', 'framer-motion', '@rivet-gg/icons', diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index e6f4cf37..a9215330 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -26,6 +26,7 @@ const footer = { product: VISIBLE_PRODUCTS.map((item) => ({ name: item.name, href: `/${item.id}` })), company: [ { name: "Cloud Pricing", href: "/cloud" }, + { name: "Bring Your Own Cloud", href: "/cloud/byoc" }, { name: "Enterprise", href: "/enterprise" }, { name: "Careers", href: "/careers" }, { name: "Talk to an engineer", href: "/talk-to-an-engineer" }, diff --git a/src/components/marketing/DeploymentOptions.tsx b/src/components/marketing/DeploymentOptions.tsx index 140a487f..3f0433ad 100644 --- a/src/components/marketing/DeploymentOptions.tsx +++ b/src/components/marketing/DeploymentOptions.tsx @@ -37,10 +37,12 @@ export const DeploymentOptions = ({

- Install {productName} and run it locally while you build. + Install {productName} and run it locally while you build. When you ship, + run the same open-source control plane as a Rust binary or container on + your own infrastructure.

@@ -51,6 +53,13 @@ export const DeploymentOptions = ({ > Open the quickstart + + Read self-hosting docs + +
@@ -77,15 +86,16 @@ export const DeploymentOptions = ({

- Run the open-source Rivet control plane as a Rust binary or container on - your infrastructure. + Run the control plane inside your own VPC, fully managed by Rivet. Your + data never leaves your cloud and there is no inbound management + connection.

- - Read self-hosting docs + + Read the BYOC docs
diff --git a/src/components/marketing/diagrams/deploymentDiagrams.tsx b/src/components/marketing/diagrams/deploymentDiagrams.tsx index 513a6165..141ae981 100644 --- a/src/components/marketing/diagrams/deploymentDiagrams.tsx +++ b/src/components/marketing/diagrams/deploymentDiagrams.tsx @@ -2,30 +2,28 @@ import { motion, useInView, useReducedMotion } from 'framer-motion'; import { useRef } from 'react'; -import { Icon, faRailway, faVercel, faCloudflare, faAws, faKubernetes } from '@rivet-gg/icons'; // --------------------------------------------------------------------------- // The three deployment models drawn on one shared axis: every variant is the // identical backend → control plane → storage stack, and only the containment // boundary moves. Pine names what Rivet Cloud runs, ink names what you run, -// and the dashed arrow is the backend dialling out across the network +// and the dashed arrow is the BYOC operator dialling out across the network // boundary. Pine only — no ember; the page's one accent CTA lives elsewhere. -// Restated from the self-host docs (src/content/self-host/index.mdx). +// Restated from the self-host docs (src/content/self-host/index.mdx) and the +// BYOC docs (/cloud/byoc/). // --------------------------------------------------------------------------- -type DeploymentDiagramVariant = 'managed' | 'byoc' | 'self-hosted'; +export type DeploymentDiagramVariant = 'managed' | 'byoc' | 'self-hosted'; const DEPLOYMENT_DIAGRAM_ARIA: Record = { managed: 'Fully managed architecture: your backend, the control plane, and storage all run inside Rivet Cloud.', byoc: - 'Bring-your-own-compute architecture: your backend runs on your infrastructure — such as Railway, Vercel, Cloudflare, AWS, or Kubernetes — and connects outbound to the control plane and storage in Rivet Cloud.', + 'Bring-your-own-cloud architecture: your backend, the control plane, and storage all run inside your VPC. A Rivet operator connects outbound to Rivet Cloud, which manages updates and maintenance.', 'self-hosted': 'Fully self-hosted architecture: your backend, the control plane, and storage all run on infrastructure you control.', }; -const COMPUTE_PROVIDERS = [faRailway, faVercel, faCloudflare, faAws, faKubernetes]; - // Staged fade-in gated on scroll-into-view; settled instantly under reduced // motion. Same hook as workflowDiagrams.tsx. const useDiagram = () => { @@ -95,8 +93,8 @@ const Zone = ({ ); // The one edge that crosses the network boundary: a dashed arrow pointing -// from your backend into Rivet Cloud (your backend dials out; no inbound -// path to your infra). The shaft sits on the diagram's center axis — the +// from your VPC into Rivet Cloud (the operator dials out; no inbound path +// to your infra). The shaft sits on the diagram's center axis — the // label hangs off it absolutely so it cannot push the arrow off-center. const CrossConnector = ({ show, at }: { show: Show; at: number }) => ( @@ -114,6 +112,7 @@ const CrossConnector = ({ show, at }: { show: Show; at: number }) => ( const Backend = () => ; const ControlPlane = () => ; const Storage = () => ; +const Management = () => ; export const DeploymentDiagram = ({ variant }: { variant: DeploymentDiagramVariant }) => { const { ref, show } = useDiagram(); @@ -127,30 +126,25 @@ export const DeploymentDiagram = ({ variant }: { variant: DeploymentDiagramVaria > {variant === 'byoc' ? ( <> - + - - - - - + + - - + + + + + + + + ) : ( { - const deploymentModels = [ + const deploymentModels: Array<{ + title: string; + description: string; + hint: string; + icon: typeof faCloudArrowUp; + diagram: DeploymentDiagramVariant; + cta: string; + href: string; + primary: boolean; + secondaryLink?: { label: string; href: string }; + }> = [ { - title: 'Fully managed', - description: 'Rivet Cloud runs your backend, the control plane, and storage. Nothing to operate.', + title: 'Rivet Cloud', + description: 'Rivet Cloud runs the control plane and storage. Run your backend on Rivet Cloud too, or bring your own compute from Vercel, Railway, Cloudflare, AWS, or Kubernetes.', hint: 'Best for most teams shipping to production.', icon: faCloudArrowUp, - diagram: 'managed' as const, + diagram: 'managed', cta: 'Get Started', href: 'https://dashboard.rivet.dev', primary: true, + secondaryLink: { label: 'Connect your own compute', href: '/actors/self-host/' }, }, { - title: 'Bring your own compute', - description: 'Your backend runs on your own infrastructure and connects outbound to the control plane in Rivet Cloud.', - hint: 'Best for serverless platforms and keeping compute in your VPC.', - icon: faShareNodes, - diagram: 'byoc' as const, - cta: 'Connect Your Host', - href: '/actors/self-host/', + title: 'Bring your own cloud', + description: 'The whole stack runs inside your VPC, and Rivet manages it for you over an outbound connection.', + hint: 'Best for data residency without anything to operate.', + icon: faCloudCheck, + diagram: 'byoc', + cta: 'Read the BYOC Docs', + href: '/cloud/byoc/', primary: false, }, { @@ -70,7 +81,7 @@ const SelfHostingComparison = () => { description: 'You run the entire stack on infrastructure you control, including air-gapped networks.', hint: 'Best for strict compliance and air-gapped environments.', icon: faServer, - diagram: 'self-hosted' as const, + diagram: 'self-hosted', cta: 'View on GitHub', href: 'https://github.com/rivet-dev/rivet', primary: false, @@ -112,12 +123,23 @@ const SelfHostingComparison = () => {

{model.hint}

- - {model.cta} - +
+ + {model.cta} + + {model.secondaryLink ? ( + + {model.secondaryLink.label} + + + ) : null} +
))} @@ -130,7 +152,7 @@ const SelfHostingComparison = () => {

Enterprise Edition

- Add multi-tenancy, access controls, backups, and deployment guidance to a self-hosted deployment. + Add multi-tenancy, access controls, backups, and deployment guidance to a BYOC or self-hosted deployment.

Explore Enterprise diff --git a/src/components/marketing/sections/AgentStackSection.tsx b/src/components/marketing/sections/AgentStackSection.tsx index 0595ff54..5807acc3 100644 --- a/src/components/marketing/sections/AgentStackSection.tsx +++ b/src/components/marketing/sections/AgentStackSection.tsx @@ -634,8 +634,26 @@ ${answeringLayers // cards in normal document flow so every need remains readable below it, // including the taller agentOS step, without introducing a nested scroll // container. + // On phones each card snaps to the slot between the diagram and the bottom + // of the screen and grows to fill it. The root is the snap container (the + // cards live in normal flow), with `proximity` so the rest of the page + // scrolls freely and only settles when a card is already close. `[data-agent-stack-story]{ --agent-stack-sticky-top:calc(var(--header-height,3.5rem) + 2.5rem); + --agent-stack-panel-height:38svh; + --agent-stack-card-top:calc(var(--agent-stack-sticky-top) + var(--agent-stack-panel-height) + 1.5rem); + } + @media (max-width: 47.999rem){ + html{scroll-snap-type:y proximity;} + [data-agent-stack-step]{ + scroll-snap-align:start; + scroll-margin-top:var(--agent-stack-card-top); + } + [data-agent-stack-card]{ + display:flex; + flex-direction:column; + min-height:calc(100svh - var(--agent-stack-card-top) - 1rem); + } } [data-agent-stack-story] [data-mobile-plate]{ opacity:.24; @@ -811,7 +829,7 @@ const MobileStackStory = () => ( className="pointer-events-none absolute inset-x-0 bottom-full h-10 bg-paper" />
@@ -852,7 +870,8 @@ const MobileStackStory = () => (
-
+ {/* Grows on phones so the solution line anchors to the card's bottom. */} +

What agents need

diff --git a/src/components/marketing/sections/OnPremSection.tsx b/src/components/marketing/sections/OnPremSection.tsx index 51609092..94774faa 100644 --- a/src/components/marketing/sections/OnPremSection.tsx +++ b/src/components/marketing/sections/OnPremSection.tsx @@ -11,11 +11,11 @@ import { const pillars = [ { title: "Single Rust binary", - body: "One process to install, monitor, and upgrade.", + body: "One process to install, monitor, and upgrade, on Kubernetes with the Enterprise Helm chart or as a systemd unit.", }, { - title: "Kubernetes or systemd", - body: "Deploy with the Enterprise Helm chart or a systemd unit.", + title: "Managed in your VPC", + body: "With BYOC, Rivet deploys and maintains the control plane inside your cloud over an outbound connection.", }, { title: "Storage you control", diff --git a/src/components/marketing/sections/SolutionsSection.module.css b/src/components/marketing/sections/SolutionsSection.module.css new file mode 100644 index 00000000..a51a012d --- /dev/null +++ b/src/components/marketing/sections/SolutionsSection.module.css @@ -0,0 +1,422 @@ +/* Hover playback for the solution-card vignettes. At rest every vignette is a + finished still; hovering (or keyboard-focusing) the card replays the story + once: terminal lines type in, steps tick, wires draw, messages arrive, a + cursor sweeps. Pure CSS so the section stays hook-free and unhydrated. + Only on pointer devices that can hover, and never under reduced motion. + Stagger comes from a per-element `--i` index set inline. */ + +@media (hover: hover) and (prefers-reduced-motion: no-preference) { + /* Every vignette resets to its opening frame the instant the hover begins. + Do not fade the whole stage to cover that cut: the ink panels going + transparent for a frame reads as a bright flash, far worse than the cut. */ + + /* Coding agent: terminal replays line by line, status dot blinks. */ + .card:hover .typeLine, + .card:focus-visible .typeLine { + animation: type-in 200ms ease-out both; + animation-delay: calc(var(--i, 0) * 240ms + 80ms); + } + .card:hover .runDot, + .card:focus-visible .runDot { + animation: blink 900ms steps(2, end) infinite; + } + + /* App builder: prompt lands, steps tick through, the app rises and its rows fill in. */ + .card:hover .prompt, + .card:focus-visible .prompt { + animation: type-in 260ms ease-out both; + } + .card:hover .step, + .card:focus-visible .step { + animation: type-in 220ms ease-out both; + animation-delay: calc(var(--i, 0) * 280ms + 320ms); + } + .card:hover .panel, + .card:focus-visible .panel { + animation: rise 420ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 1200ms; + } + .card:hover .row, + .card:focus-visible .row { + animation: type-in 240ms ease-out both; + animation-delay: calc(var(--i, 0) * 160ms + 1550ms); + } + .card:hover .liveDot, + .card:focus-visible .liveDot { + animation: pulse 1.4s ease-in-out infinite; + animation-delay: 1200ms; + } + + /* Company agent: a ticket lands, the agent wakes, wires draw out to each + system, systems light up, the resolution posts. */ + .card:hover .ticket, + .card:focus-visible .ticket { + animation: flash 700ms ease-out both; + } + .card:hover .ticketDot, + .card:focus-visible .ticketDot { + animation: ping 900ms ease-out both; + } + .card:hover .agentBox, + .card:focus-visible .agentBox { + animation: connect 320ms ease-out both; + animation-delay: 350ms; + } + .card:hover .wire, + .card:focus-visible .wire { + transform-origin: left center; + animation: grow 450ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: calc(var(--i, 0) * 260ms + 500ms); + } + .card:hover .system, + .card:focus-visible .system { + animation: connect 320ms ease-out both; + animation-delay: calc(var(--i, 0) * 260ms + 860ms); + } + .card:hover .resultChip, + .card:focus-visible .resultChip { + transform-origin: left center; + animation: pop 320ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 1800ms; + } + .card:hover .connectedDot, + .card:focus-visible .connectedDot { + animation: pulse 1.4s ease-in-out infinite; + animation-delay: 1300ms; + } + + /* Personal agent: the schedule fires, the request lands, the agent types, + replies, pins the reminder, and the memory count ticks up. */ + .card:hover .schedule, + .card:focus-visible .schedule { + animation: flash 700ms ease-out both; + } + .card:hover .scheduleDot, + .card:focus-visible .scheduleDot { + animation: ping 900ms ease-out both; + } + .card:hover .bubbleOut, + .card:focus-visible .bubbleOut { + animation: slide-from-right 340ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 350ms; + } + .card:hover .typing, + .card:focus-visible .typing { + animation: hold 1000ms ease-in-out both; + animation-delay: 650ms; + } + .card:hover .typingDot, + .card:focus-visible .typingDot { + animation: bob 700ms ease-in-out infinite; + animation-delay: calc(var(--i, 0) * 140ms); + } + .card:hover .bubbleIn, + .card:focus-visible .bubbleIn { + animation: slide-from-left 380ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 1650ms; + } + .card:hover .chip, + .card:focus-visible .chip { + transform-origin: left center; + animation: pop 320ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 2150ms; + } + .card:hover .countOld, + .card:focus-visible .countOld { + animation: count-out 380ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 2250ms; + } + .card:hover .countNew, + .card:focus-visible .countNew { + animation: count-in 380ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 2250ms; + } + + /* Realtime: collaborators arrive, JM types a line, the box ticks, RS types + the next line, sync settles. A typed line is one clip-path wipe over the + full text plus one marker (caret + name tag) that sweeps from the left + edge to the right edge on the same linear clock, so the text edge and the + marker move together and the tag glides instead of jumping. The line + always occupies its full width, so nothing reflows while typing. */ + .card:hover .avatar, + .card:focus-visible .avatar { + animation: pop 320ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: calc(var(--i, 0) * 120ms + 80ms); + } + .card:hover .typedText, + .card:focus-visible .typedText { + animation: wipe var(--dur) linear var(--start) both; + } + .card:hover .typedMarker, + .card:focus-visible .typedMarker { + animation: + sweep var(--dur) linear var(--start) both, + appear var(--dur) step-start var(--start) both; + } + .card:hover .tick, + .card:focus-visible .tick { + animation: tick 260ms ease-out both; + animation-delay: 1650ms; + } + .card:hover .tickMark, + .card:focus-visible .tickMark { + animation: pop 260ms cubic-bezier(0.2, 0.8, 0.2, 1) both; + animation-delay: 1720ms; + } + .card:hover .synced, + .card:focus-visible .synced { + animation: hidden-while 2500ms step-end both; + animation-delay: 450ms; + } + .card:hover .syncing, + .card:focus-visible .syncing { + animation: shown-while 2500ms step-end both; + animation-delay: 450ms; + } +} + +/* Typed line: full-width inline box; the marker hangs off its right edge at + rest and sweeps across it on hover. */ +.typed { + position: relative; + display: inline-block; + white-space: pre; +} +.typedMarker { + position: absolute; + top: 50%; + left: 100%; + display: flex; + align-items: center; + gap: 0.25rem; + transform: translateY(-50%); + pointer-events: none; +} +.typedCaret { + width: 1px; + height: 1.15em; + background: var(--caret); +} +.typedTag { + padding: 1px 4px; + border-radius: 2px; + background: var(--caret); + color: #fff; + font-size: 8px; + font-weight: 500; + line-height: 1; + white-space: nowrap; +} + +@keyframes wipe { + from { + clip-path: inset(0 100% 0 0); + } + to { + clip-path: inset(0 0 0 0); + } +} +@keyframes sweep { + from { + left: 0; + } + to { + left: 100%; + } +} +/* Hidden until its start; `step-start` (not step-end) so a rounding-short + finish can never leave it stuck on the `from` frame. */ +@keyframes appear { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes type-in { + from { + opacity: 0; + transform: translateY(3px); + } + to { + opacity: 1; + transform: none; + } +} + +@keyframes blink { + 50% { + opacity: 0.25; + } +} + +@keyframes pulse { + 50% { + opacity: 0.35; + transform: scale(0.8); + } +} + +@keyframes rise { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: none; + } +} + +@keyframes grow { + from { + transform: scaleX(0); + } + to { + transform: scaleX(1); + } +} + +@keyframes connect { + from { + border-color: rgb(27 25 22 / 0.1); + color: #56524a; + } + to { + border-color: rgb(46 64 52 / 0.6); + color: #1b1916; + } +} + +@keyframes flash { + 0% { + background-color: rgb(46 64 52 / 0.12); + } + 100% { + background-color: #efefef; + } +} + +@keyframes slide-from-right { + from { + opacity: 0; + transform: translateX(10px); + } + to { + opacity: 1; + transform: none; + } +} + +@keyframes slide-from-left { + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: none; + } +} + +@keyframes pop { + from { + opacity: 0; + transform: scale(0.6); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes tick { + from { + background-color: transparent; + } + to { + background-color: #2e4034; + } +} + +/* Quick expand-and-settle ring for a scheduled trigger firing. */ +@keyframes ping { + 0% { + box-shadow: 0 0 0 0 rgb(46 64 52 / 0.45); + } + 100% { + box-shadow: 0 0 0 7px rgb(46 64 52 / 0); + } +} + +/* Visible only for the middle of its run (used for transient states such as + a typing indicator or a "syncing…" label). */ +@keyframes hold { + 0%, + 100% { + opacity: 0; + } + 12%, + 88% { + opacity: 1; + } +} + +/* Inverse of `hold` with hard edges, for the label that gets replaced. */ +@keyframes hidden-while { + 0%, + 100% { + opacity: 1; + } + 1%, + 99% { + opacity: 0; + } +} + +@keyframes shown-while { + 0%, + 100% { + opacity: 0; + } + 1%, + 99% { + opacity: 1; + } +} + +@keyframes bob { + 0%, + 100% { + transform: translateY(0); + opacity: 0.5; + } + 40% { + transform: translateY(-2px); + opacity: 1; + } +} + +@keyframes count-out { + from { + opacity: 1; + transform: none; + } + to { + opacity: 0; + transform: translateY(-100%); + } +} + +@keyframes count-in { + from { + opacity: 0; + transform: translateY(100%); + } + to { + opacity: 1; + transform: none; + } +} diff --git a/src/components/marketing/sections/SolutionsSection.tsx b/src/components/marketing/sections/SolutionsSection.tsx new file mode 100644 index 00000000..efe31aff --- /dev/null +++ b/src/components/marketing/sections/SolutionsSection.tsx @@ -0,0 +1,582 @@ +import { Fragment, type CSSProperties, type ReactNode } from "react"; +import { ProductBadge } from "@/components/ProductBar"; +import { canonicalizeInternalHref } from "@/lib/internalHref"; +import { products, type Product } from "@/sitemap/products"; +import { SITE_SECTION_CLASS, SITE_STANDARD_RAIL_CLASS } from "../layout"; +import { CARD_TITLE_CLASS, SECTION_H2_CLASS } from "../typography"; +import styles from "./SolutionsSection.module.css"; + +// Solutions catalog: what people build on Rivet, one card per kind of +// product. Each card names the shape of the workload, the products it leans +// on, and a small hand-built vignette of the thing itself. The grid is two +// wide plates over three narrow ones so the two biggest stories get the room. +// Hook-free so the landing page renders it with no client directive. Each +// vignette replays its story on card hover via SolutionsSection.module.css; +// `stagger(i)` sets the per-element delay index those keyframes read. + +type ProductId = "actors" | "agentos" | "workflows" | "dynamic-apps"; + +interface Solution { + id: string; + title: string; + href: string; + linkLabel: string; + uses: ProductId[]; + vignette: ReactNode; + wide?: boolean; +} + +const stagger = (index: number) => ({ "--i": index }) as CSSProperties; + +const productById = (id: ProductId): Product | undefined => + products.find((product) => product.id === id); + +// Vignette frame on porcelain: white fill, hairline, clipped. Ink variants +// (terminal) paint their own surface. +const VIGNETTE_CLASS = `relative flex flex-1 flex-col overflow-hidden rounded-lg border border-ink/10 bg-white text-[12px] leading-snug text-ink-soft`; + +const MiniChromeBar = ({ children }: { children?: ReactNode }) => ( +
+
+); + +// Coding agent working a task inside its own computer: run, fail, patch, +// pass, open the PR. Flat ink, per the code-and-terminal rule. +const CodingAgentVignette = () => ( +
+
+ +
+
+

+ Fix the flaky test in + checkout.spec.ts +

+

+ $ pnpm vitest run checkout +

+

+ {" "}12 passed · 1 failed · + total.ts:41 rounding +

+

+ ~ src/checkout/total.ts{" "} + +3{" "} + −1 +

+

+ $ pnpm vitest run checkout +

+

+ {" "}13 passed +

+

+ Opened PR #482 · resumed after 2 + restarts +

+
+
+); + +// Prompt in, deployed app out. The builder's user describes a tool; the +// generated backend ships as its own release and the app fills with data. +const AppBuilderVignette = () => { + const quotes = [ + { job: "Hendricks · full replacement", amount: "$12,400", stage: "Sent" }, + { job: "Okafor · re-shingle", amount: "$8,900", stage: "New" }, + { job: "Lindqvist · leak repair", amount: "$3,250", stage: "Won" }, + ]; + return ( +
+ + + northwind-crm.example.app + + +
+
+
+ Build a CRM for my roofing business with a quotes pipeline. +
+
    + {[ + "Generated schema · 4 tables", + "Wrote API · 11 routes", + "Deployed release v3", + ].map((step, i) => ( +
  • + {step} +
  • + ))} +
+

+ Release v3 · live for 2 users +

+
+
+
+ Quotes + + +
+
+ {[ + ["New", "6"], + ["Sent", "3"], + ["Won", "9"], + ].map(([stage, count]) => ( +
+ {stage} + {count} +
+ ))} +
+
    + {quotes.map((quote, i) => ( +
  • + + {quote.job} + + + {quote.amount} + + + {quote.stage} + +
  • + ))} +
+
+
+
+ ); +}; + +// An agent inside the company boundary, reading the systems it is allowed to. +const CompanyAgentVignette = () => ( +
+
+ + Your VPC · no data leaves your cloud + +
+ + + Helpdesk · 2m +
+ {/* Wire and system share a grid row so each wire ends at its chip at any width. */} +
+
+
Agent
+
Support triage
+
+ {["Postgres", "Orders API", "Helpdesk"].map((system, i) => ( + + + ))} +
+
+

+ + Refund issued · ticket resolved + + + +

+
+); + +const CheckGlyph = ({ className }: { className?: string }) => ( + +); + +// One long-lived agent per person: a standing schedule, a conversation that +// picks up where it left off, memory that outlives any single request. On +// hover the schedule fires, the request lands, the agent types, replies, sets +// the reminder, and the memory count ticks up by one. +const PersonalAgentVignette = () => ( +
+
+ + + weekdays · 07:00 +
+
+
+ Move my 3pm with Sam to tomorrow. +
+
+ +
+ Done. Sam accepted 10am tomorrow, and I'll remind you at 9:45. + + Reminder · tomorrow 9:45 + +
+
+
+

+ Online 214 days + + + + 1,205 memories + + +

+
+); + +// A line of text typed by a named collaborator. On hover the text is revealed +// by a clip-path wipe while one marker (caret + name tag) sweeps across the +// line on the same linear clock, so the tag glides and the text edge always +// sits at the caret. `startMs`/`stepMs` set the delay and per-character pace +// consumed by SolutionsSection.module.css. +const TypedLine = ({ + text, + tag, + caretClass, + startMs, + stepMs, +}: { + text: string; + tag: string; + caretClass: string; + startMs: number; + stepMs: number; +}) => ( + + {text} + +); + +// Several people in one document, one authoritative state. On hover the +// collaborators arrive, JM types the second line, the item +// ticks, RS types a new line, and the sync settles. +const RealtimeVignette = () => { + const people = [ + { initials: "AK", fill: "bg-pine" }, + { initials: "JM", fill: "bg-product-actors" }, + { initials: "RS", fill: "bg-product-workflows" }, + ]; + return ( +
+ + + Q3 launch plan + + + +
+

Launch checklist

+
    +
  • + + Ship pricing page +
  • +
  • + + +
  • +
  • +
  • +
  • +
  • +
+

+ + + + + synced · 12 ms + + + +

+
+
+ ); +}; + +const solutions: Solution[] = [ + { + id: "coding-agents", + title: "Coding agents", + href: "/agentos/", + linkLabel: "Explore agentOS", + uses: ["agentos", "actors", "workflows"], + vignette: , + wide: true, + }, + { + id: "agent-app-builders", + title: "Agent app builders", + href: "/dynamic-apps/", + linkLabel: "Explore Dynamic Apps", + uses: ["dynamic-apps", "actors"], + vignette: , + wide: true, + }, + { + id: "company-agents", + title: "Company-specific agents", + href: "/enterprise/", + linkLabel: "Explore Enterprise", + uses: ["actors", "agentos"], + vignette: , + }, + { + id: "personal-agents", + title: "Personal agents", + href: "/actors/", + linkLabel: "Explore Actors", + uses: ["actors", "workflows"], + vignette: , + }, + { + id: "realtime-apps", + title: "Realtime apps", + href: "/actors/", + linkLabel: "Explore Actors", + uses: ["actors"], + vignette: , + }, +]; + +const SolutionCard = ({ solution }: { solution: Solution }) => ( +
+
+

{solution.title}

+
    + {solution.uses.map((id) => { + const product = productById(id); + if (!product) return null; + return ( +
  • + + {product.name} +
  • + ); + })} +
+
+
{solution.vignette}
+ + {solution.linkLabel} + + +
+); + +export const SolutionsSection = () => ( +
+
+
+

+ Built for every kind of agent. +

+
+ +
    + {solutions.map((solution) => ( +
  • + +
  • + ))} +
+
+
+); diff --git a/src/pages/enterprise.astro b/src/pages/enterprise.astro index c6ae0733..96613869 100644 --- a/src/pages/enterprise.astro +++ b/src/pages/enterprise.astro @@ -24,7 +24,7 @@ import { const title = 'Enterprise Infrastructure'; const description = - 'Run Rivet on-prem, in your VPC, inside customer environments, or fully air-gapped with an enterprise control plane and no required outbound connections.'; + 'Run Rivet in your VPC with BYOC, on-prem, inside customer environments, or fully air-gapped with an enterprise control plane and no required outbound connections.'; const canonicalUrl = 'https://rivet.dev/enterprise/'; const breadcrumbSchema = { @@ -68,6 +68,21 @@ const footprints = [ }, ]; +const deploymentPaths = [ + { + title: 'Bring your own cloud', + body: 'Rivet deploys and maintains the control plane and storage inside your VPC over an outbound connection. Your data stays in your cloud, and there is nothing for your team to operate.', + href: '/cloud/byoc/', + linkLabel: 'Read the BYOC docs', + }, + { + title: 'Self-host', + body: 'Run the control plane yourself on Kubernetes or systemd, with no required connection to Rivet Cloud. The path for air-gapped and sovereign-cloud deployments.', + href: '/actors/self-host/control-plane/', + linkLabel: 'Read self-hosting docs', + }, +]; + const enterpriseEditionFeatures = [ 'Actor control plane', 'FoundationDB persistence layer', @@ -98,7 +113,8 @@ const enterpriseEditionFeatures = [

Run Rivet inside your perimeter.

Keep runtime, control plane, and data under the controls your team already - operates. The same platform that powers Rivet Cloud. + operates, managed by Rivet in your VPC or run entirely by you. The same + platform that powers Rivet Cloud.

@@ -118,6 +134,36 @@ const enterpriseEditionFeatures = [ + +
+
+
+

Two ways to run it inside your perimeter.

+

+ Both run the same Enterprise Edition control plane. Choose whether Rivet operates it or your team does. +

+
+
+ { + deploymentPaths.map((path) => ( +
+ +

{path.body}

+
+
+ )) + } +
+
+
+ + +
@@ -210,13 +256,13 @@ const enterpriseEditionFeatures = [ Talk to an engineer - - Read self-hosting docs + + Read the BYOC docs diff --git a/src/pages/index.astro b/src/pages/index.astro index b9d374c0..dfea89c8 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -6,6 +6,7 @@ import MarketingLayout from '@/layouts/MarketingLayout.astro'; import { RedesignedHero } from '@/components/marketing/sections/RedesignedHero'; import { StackSection } from '@/components/marketing/sections/StackSection'; import { AgentStackSection } from '@/components/marketing/sections/AgentStackSection'; +import { SolutionsSection } from '@/components/marketing/sections/SolutionsSection'; import { ObservabilitySection } from '@/components/marketing/sections/ObservabilitySection'; import { IntegrationsSection } from '@/components/marketing/sections/IntegrationsSection'; import { HostingSection } from '@/components/marketing/sections/HostingSection'; @@ -129,6 +130,8 @@ const latestPost = latestEntry + + From 5422a637043b862a5a34237de9cd7fdccc6b9c67 Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 15 Sep 2026 04:17:25 +0000 Subject: [PATCH 2/2] enterprise: keep meta description within 160 chars Amp-Thread-ID: https://ampcode.com/threads/T-01a0a1bf-48c6-77f8-b65d-0b681d9f18a9 Co-authored-by: Nicholas Kissel --- src/pages/enterprise.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/enterprise.astro b/src/pages/enterprise.astro index 96613869..cacb09e8 100644 --- a/src/pages/enterprise.astro +++ b/src/pages/enterprise.astro @@ -24,7 +24,7 @@ import { const title = 'Enterprise Infrastructure'; const description = - 'Run Rivet in your VPC with BYOC, on-prem, inside customer environments, or fully air-gapped with an enterprise control plane and no required outbound connections.'; + 'Run Rivet in your VPC with BYOC, on-prem, inside customer environments, or fully air-gapped with an enterprise control plane and no outbound connections.'; const canonicalUrl = 'https://rivet.dev/enterprise/'; const breadcrumbSchema = {