From 63e6b7af617c0ffce7ad6fa3e3c5f20190abed82 Mon Sep 17 00:00:00 2001 From: Nicholas Kissel Date: Fri, 28 Aug 2026 22:45:24 -0700 Subject: [PATCH] feat(marketing): refine homepage storytelling and visuals --- src/components/marketing/ProductMotif.tsx | 298 +++--- .../actors/ActorsObservabilitySection.tsx | 6 +- .../diagrams/ConsolidatedStackDiagram.tsx | 231 ----- .../diagrams/DeploymentBoundaryDiagram.tsx | 136 +++ .../EnterpriseObservabilitySection.tsx | 6 +- .../marketing/sections/AgentStackSection.tsx | 978 ++++++++++++++++++ .../marketing/sections/HostingSection.tsx | 25 +- .../sections/IntegrationsSection.tsx | 57 +- .../sections/ObservabilitySection.tsx | 6 +- .../marketing/sections/OnPremSection.tsx | 49 +- .../marketing/sections/RedesignedHero.tsx | 154 ++- .../marketing/startups/StartupsPage.tsx | 5 +- src/layouts/BaseLayout.astro | 4 +- src/pages/dynamic-apps.astro | 4 +- src/pages/enterprise.astro | 6 +- src/pages/index.astro | 139 ++- src/pages/workflows.astro | 4 +- src/styles/main.css | 454 ++++---- 18 files changed, 1903 insertions(+), 659 deletions(-) delete mode 100644 src/components/marketing/diagrams/ConsolidatedStackDiagram.tsx create mode 100644 src/components/marketing/diagrams/DeploymentBoundaryDiagram.tsx create mode 100644 src/components/marketing/sections/AgentStackSection.tsx diff --git a/src/components/marketing/ProductMotif.tsx b/src/components/marketing/ProductMotif.tsx index 9fe376b..5ab6416 100644 --- a/src/components/marketing/ProductMotif.tsx +++ b/src/components/marketing/ProductMotif.tsx @@ -9,16 +9,38 @@ export interface ProductMotifProps { surface: "card" | "hero"; } -const workflowSteps = [ - { x: 164, y: 84, width: 108, height: 16 }, - { x: 44, y: 132, width: 132, height: 16 }, - { x: 144, y: 180, width: 128, height: 16 }, - { x: 60, y: 228, width: 132, height: 16 }, +// Workflows draws the execution route as a bundle of three nested serpentine +// lanes — no task boxes, just the path. Each lane is the same meander offset +// by ±10: straight runs shift vertically and turn radii grow or shrink so the +// bundle stays parallel through every U-turn, flipping sides like nested +// hairpins. +// `lane` selects the per-lane pulse keyframes in main.css, whose stops are +// derived from this exact geometry (each lane's arc radii differ, so its +// corner positions land at different path fractions). +const workflowLanes = [ + { lane: 1, offset: -14, tone: "soft", delay: 0 }, + { lane: 2, offset: 0, tone: "bright", delay: 140 }, + { lane: 3, offset: 14, tone: "soft", delay: 280 }, ] as const; -const heroWorkflowStepY = [20, 110, 250, 340] as const; +// Both ends run 180 units off-canvas so the pulse dash (0.1 of the path, +// ~161-169 units) sits fully off-screen at the loop's extremes. +const workflowLanePath = (offset: number) => { + const rightRadius = 38 - offset; + const leftRadius = 38 + offset; + return [ + `M -180 ${48 + offset}`, + "H 262", + `a ${rightRadius} ${rightRadius} 0 0 1 0 ${rightRadius * 2}`, + "H 58", + `a ${leftRadius} ${leftRadius} 0 0 0 0 ${leftRadius * 2}`, + "H 262", + `a ${rightRadius} ${rightRadius} 0 0 1 0 ${rightRadius * 2}`, + "H -180", + ].join(" "); +}; -const dynamicAppCircles = [ +const agentOSCircles = [ { cx: 16, cy: 36, direction: "forward", tone: "bright", delay: 0 }, { cx: 96, cy: 108, direction: "backward", tone: "soft", delay: 0 }, { cx: 176, cy: 36, direction: "forward", tone: "soft", delay: 120 }, @@ -29,14 +51,70 @@ const dynamicAppCircles = [ { cx: 256, cy: 252, direction: "backward", tone: "soft", delay: 300 }, ] as const; -const easeInOutSine = (progress: number) => - (1 - Math.cos(Math.PI * progress)) / 2; +// Dynamic Apps runs a verified Conway's Game of Life board on a 10-column +// grid (pitch 32 across the shared 320×400 viewBox — chips sized to match +// the weight of the neighboring card motifs). `life` encodes each cell's +// alive-timeline: "on" = alive every generation; "a"/"b" = the two phases of +// a period-2 oscillator; "q1"-"q3" = alive for a 1-3 generation window +// starting at `phase` of the glider's 4-generation cycle. Every coordinate is +// a genuine B3/S23 evolution and the composed board never self-interacts — +// re-verify with a Life simulation before moving any cell. +const LIFE_PITCH = 32; +const LIFE_CELL = 24; +const LIFE_INSET = 4; + +interface LifeCell { + col: number; + row: number; + life: "on" | "a" | "b" | "q1" | "q2" | "q3"; + tone: "bright" | "soft" | "still"; + phase?: number; +} + +const lifeBoardCells: readonly LifeCell[] = [ + // Toad (period 2), partly behind the glass verb badge + { col: 3, row: 2, life: "on", tone: "soft" }, + { col: 0, row: 3, life: "on", tone: "soft" }, + { col: 1, row: 2, life: "a", tone: "soft" }, + { col: 2, row: 2, life: "a", tone: "soft" }, + { col: 1, row: 3, life: "a", tone: "soft" }, + { col: 2, row: 3, life: "a", tone: "soft" }, + { col: 2, row: 1, life: "b", tone: "soft" }, + { col: 0, row: 2, life: "b", tone: "soft" }, + { col: 3, row: 3, life: "b", tone: "soft" }, + { col: 1, row: 4, life: "b", tone: "soft" }, + // Beacon (period 2), bright cells at the collapsing hinge + { col: 6, row: 0, life: "on", tone: "soft" }, + { col: 7, row: 0, life: "on", tone: "soft" }, + { col: 6, row: 1, life: "on", tone: "soft" }, + { col: 9, row: 2, life: "on", tone: "soft" }, + { col: 8, row: 3, life: "on", tone: "soft" }, + { col: 9, row: 3, life: "on", tone: "soft" }, + { col: 7, row: 1, life: "a", tone: "bright" }, + { col: 8, row: 2, life: "a", tone: "bright" }, + // Still life: a block anchoring the left flank + { col: 0, row: 7, life: "on", tone: "still" }, + { col: 1, row: 7, life: "on", tone: "still" }, + { col: 0, row: 8, life: "on", tone: "still" }, + { col: 1, row: 8, life: "on", tone: "still" }, +]; + +// The glider's 10-cell union in its local frame. The group walks one diagonal +// cell per 4-generation cycle from the board origin toward the fade mask. +const gliderCells: readonly LifeCell[] = [ + { col: 1, row: 0, life: "q1", tone: "bright", phase: 0 }, + { col: 0, row: 1, life: "q1", tone: "bright", phase: 1 }, + { col: 1, row: 1, life: "q1", tone: "bright", phase: 3 }, + { col: 2, row: 1, life: "q3", tone: "bright", phase: 0 }, + { col: 0, row: 2, life: "a", tone: "bright" }, + { col: 1, row: 2, life: "q2", tone: "bright", phase: 0 }, + { col: 2, row: 2, life: "on", tone: "bright" }, + { col: 3, row: 2, life: "q1", tone: "bright", phase: 3 }, + { col: 1, row: 3, life: "q3", tone: "bright", phase: 1 }, + { col: 2, row: 3, life: "q2", tone: "bright", phase: 2 }, +]; -const agentOSRings = Array.from({ length: 9 }, (_, index) => ({ - scale: 0.06 + easeInOutSine(index / 9) * 1.1, - delay: index * -800, - tone: index % 3 === 0 ? "bright" : "soft", -})); +const GLIDER_ORIGIN = { col: 5, row: 7 } as const; const actorLogoMarks = [ { @@ -172,134 +250,118 @@ const containerClassName = ( .filter(Boolean) .join(" "); -// Rails stop at each node instead of relying on a product-colored cutout. This -// keeps the card unchanged while allowing the same geometry to sit over the -// hero's depth wash without painting flat rectangles into the paper field. -const WorkflowMotif = ({ surface }: Pick) => { - const renderedSteps = [ - ...workflowSteps.map((step) => ({ ...step, variant: "default" as const })), - ...(surface === "hero" - ? workflowSteps.map((step, index) => ({ - ...step, - y: heroWorkflowStepY[index], - variant: "mobile" as const, - })) - : []), - ]; - - return ( - - {renderedSteps.map((positionedStep, index) => { - const sequenceIndex = index % workflowSteps.length; - const centerY = positionedStep.y + positionedStep.height / 2; - - return ( - - - - - - - - ); - })} - - ); -}; - -const DynamicAppsMotif = ({ surface }: Pick) => ( +const WorkflowMotif = ({ surface }: Pick) => ( - {dynamicAppCircles.map((circle) => ( - ( + + > + + + ))} ); +// "rest-off" marks cells dead at generation 0 so the static surfaces (hero, +// reduced motion) render the same curated frame the paused timelines resolve. +// phase !== 0 is only a valid proxy for that while no q-window wraps past the +// end of the glider cycle; keep alive-windows contiguous from their phase. +const lifeCellClassName = (cell: LifeCell) => + [ + "dynamic-app-cell", + `dynamic-app-cell--${cell.life}`, + cell.tone === "bright" ? "" : `dynamic-app-cell--${cell.tone}`, + cell.life.startsWith("q") && cell.phase !== 0 + ? "dynamic-app-cell--rest-off" + : "", + ] + .filter(Boolean) + .join(" "); + +const LifeRect = ({ + cell, + colOffset = 0, + rowOffset = 0, +}: { + cell: LifeCell; + colOffset?: number; + rowOffset?: number; +}) => ( + +); + +const DynamicAppsMotif = ({ surface }: Pick) => ( + + {lifeBoardCells.map((cell) => ( + + ))} + + {gliderCells.map((cell) => ( + + ))} + + +); + const AgentOSMotif = ({ surface }: Pick) => ( - {agentOSRings.map((ring, index) => ( + {agentOSCircles.map((circle) => ( ))} diff --git a/src/components/marketing/actors/ActorsObservabilitySection.tsx b/src/components/marketing/actors/ActorsObservabilitySection.tsx index a5a3115..5a19ad3 100644 --- a/src/components/marketing/actors/ActorsObservabilitySection.tsx +++ b/src/components/marketing/actors/ActorsObservabilitySection.tsx @@ -37,7 +37,7 @@ export const ActorsObservabilitySection = () => (
-

Observe the whole stack.

+

Complete observability.

Inspect Actor state, SQLite data, workflow progress, events, and callable actions from the Rivet dashboard. @@ -61,14 +61,14 @@ export const ActorsObservabilitySection = () => (

-
+
Rivet Actor Inspector showing state, events, and callable actions for a running Actor
diff --git a/src/components/marketing/diagrams/ConsolidatedStackDiagram.tsx b/src/components/marketing/diagrams/ConsolidatedStackDiagram.tsx deleted file mode 100644 index 7e72a82..0000000 --- a/src/components/marketing/diagrams/ConsolidatedStackDiagram.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import { - ArrowDown, - ArrowRight, - Brain, - Bug, - Clock3, - GitBranch, - Laptop, - MessagesSquare, - Network, - Radio, - Shield, -} from "lucide-react"; -import { productAccent, wordmarkMaskStyle } from "@/lib/product-accent"; -import { productLogos } from "@/sitemap/productLogos"; -import foundationDbLogo from "../images/platforms/foundationdb.svg"; -import postgresLogo from "../images/platforms/postgres.svg"; -import rivetLogo from "../images/platforms/rivet-white.svg"; - -const agentNeeds = [ - { label: "Durable processes", icon: Clock3 }, - { label: "Computers & files", icon: Laptop }, - { label: "Workflows", icon: GitBranch }, - { label: "Queues & realtime", icon: Radio }, - { label: "State & memory", icon: Brain }, - { label: "Debugging & observability", icon: Bug }, - { label: "Orchestration", icon: Network }, - { label: "Isolation", icon: Shield }, - { label: "Agent-to-agent communication", icon: MessagesSquare }, -]; - -const productMarks = [ - { - id: "actors", - label: "Actors", - logo: productLogos.actors, - }, - { - id: "agentos", - label: "agentOS", - logo: productLogos.agentos, - }, - { - id: "workflows", - label: "Workflows", - logo: productLogos.workflows, - }, - { - id: "dynamic-apps", - label: "Dynamic Apps", - logo: productLogos["dynamic-apps"], - }, -]; - -const storageMarks = [ - { label: "Postgres", src: postgresLogo.src }, - { label: "SQLite", src: "/images/registry/sqlite3.svg" }, - { label: "S3", src: "/images/registry/s3.svg" }, - { - label: "FoundationDB", - src: foundationDbLogo.src, - iconClassName: "w-7", - }, -]; - -const StackLayer = ({ - title, - detail, - highlight = false, - icon, - children, -}: { - title: string; - detail?: string; - highlight?: boolean; - icon?: React.ReactNode; - children?: React.ReactNode; -}) => ( -
-
- {icon} -

{title}

-
- {detail ? ( -

{detail}

- ) : null} - {children} -
-); - -const StackConnector = () => ( -