Files
nextjs-startup/components/vertex-landing.tsx
T

771 lines
36 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useState } from "react"
import {
Activity,
ArrowRight,
Check,
ChevronRight,
CircleCheck,
Code2,
Gauge,
Globe2,
LockKeyhole,
Network,
Route,
ShieldCheck,
Sparkles,
Waypoints,
Workflow,
Zap,
} from "lucide-react"
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { ThemeToggle } from "@/components/theme-toggle"
import { AnimatedList } from "@/components/ui/animated-list"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart"
import { FlickeringGrid } from "@/components/ui/flickering-grid"
import { Globe } from "@/components/ui/globe"
import { Marquee } from "@/components/ui/marquee"
import { OrbitingCircles } from "@/components/ui/orbiting-circles"
import { Ripple } from "@/components/ui/ripple"
import {
AnimatedSpan,
Terminal,
TypingAnimation,
} from "@/components/ui/terminal"
import { cn } from "@/lib/utils"
const providers = [
{ name: "OpenAI", mark: "OA" },
{ name: "Anthropic", mark: "A" },
{ name: "Gemini", mark: "G" },
{ name: "Meta", mark: "M" },
{ name: "Mistral", mark: "MI" },
{ name: "xAI", mark: "x" },
{ name: "Cohere", mark: "C" },
{ name: "DeepSeek", mark: "DS" },
]
const routingEvents = [
{
icon: <Route className="size-4" />,
title: "Routed to Claude Sonnet",
meta: "342ms · 8.2k tokens",
accent: "bg-violet-500/15 text-violet-500",
},
{
icon: <Zap className="size-4" />,
title: "Prompt cache hit",
meta: "Saved $0.018 · 42ms",
accent: "bg-amber-500/15 text-amber-500",
},
{
icon: <Activity className="size-4" />,
title: "Latency policy applied",
meta: "Gemini Flash · 118ms",
accent: "bg-blue-500/15 text-blue-500",
},
{
icon: <CircleCheck className="size-4" />,
title: "Fallback completed",
meta: "OpenAI → Mistral · 621ms",
accent: "bg-emerald-500/15 text-emerald-500",
},
{
icon: <ShieldCheck className="size-4" />,
title: "Guardrail passed",
meta: "No sensitive data detected",
accent: "bg-cyan-500/15 text-cyan-500",
},
]
const usageData = [
{ time: "08:00", cost: 28, latency: 54 },
{ time: "10:00", cost: 35, latency: 48 },
{ time: "12:00", cost: 31, latency: 64 },
{ time: "14:00", cost: 48, latency: 57 },
{ time: "16:00", cost: 41, latency: 72 },
{ time: "18:00", cost: 57, latency: 66 },
{ time: "20:00", cost: 52, latency: 82 },
]
const chartConfig = {
cost: { label: "Requests", color: "#2563eb" },
latency: { label: "Latency", color: "#8b5cf6" },
} satisfies ChartConfig
const reviews = [
{
initials: "MK",
name: "Maya Kim",
role: "AI Platform Lead",
quote:
"Vertex gave us one reliable interface for every model experiment. We stopped rebuilding provider integrations and started shipping.",
},
{
initials: "JL",
name: "Jon Bell",
role: "Founding Engineer",
quote:
"Fallback routing turned model outages into a non-event. Our application stays fast even when a provider is under pressure.",
},
{
initials: "AR",
name: "Ari Rao",
role: "VP of Engineering",
quote:
"The unified cost view finally made inference spend understandable across teams, providers, and environments.",
},
{
initials: "SC",
name: "Sofia Chen",
role: "Product Engineer",
quote:
"We changed one base URL and had access to the models we needed. The developer experience is exactly that simple.",
},
{
initials: "OT",
name: "Owen Taylor",
role: "Infrastructure Director",
quote:
"Vertex lets our developers move quickly while the platform team keeps control of budgets, policies, and reliability.",
},
{
initials: "NB",
name: "Nina Brooks",
role: "Applied AI Engineer",
quote:
"Model comparisons that used to take days now take minutes. The same payload works everywhere we want to test.",
},
]
const globeConfig = {
width: 800,
height: 800,
onRender: () => {},
devicePixelRatio: 2,
phi: 0,
theta: 0.28,
dark: 1,
diffuse: 1.1,
mapSamples: 12000,
mapBrightness: 4.5,
baseColor: [0.05, 0.12, 0.32] as [number, number, number],
markerColor: [0.15, 0.46, 1] as [number, number, number],
glowColor: [0.08, 0.18, 0.48] as [number, number, number],
markers: [
{ location: [37.7749, -122.4194] as [number, number], size: 0.08 },
{ location: [40.7128, -74.006] as [number, number], size: 0.06 },
{ location: [51.5072, -0.1276] as [number, number], size: 0.07 },
{ location: [48.8566, 2.3522] as [number, number], size: 0.05 },
{ location: [1.3521, 103.8198] as [number, number], size: 0.08 },
{ location: [35.6762, 139.6503] as [number, number], size: 0.06 },
{ location: [-33.8688, 151.2093] as [number, number], size: 0.05 },
{ location: [-23.5505, -46.6333] as [number, number], size: 0.06 },
],
}
function Brand({ compact = false }: { compact?: boolean }) {
return (
<span className="inline-flex items-center gap-2.5">
<span className="relative inline-flex size-8 items-center justify-center overflow-hidden rounded-[10px] bg-blue-600 text-white shadow-[0_0_28px_rgba(37,99,235,.32)]">
<Waypoints className="size-[18px]" aria-hidden="true" />
</span>
{!compact && (
<span className="text-[17px] font-semibold tracking-[-0.03em]">Vertex</span>
)}
</span>
)
}
function ProviderMark({ mark, name, small = false }: { mark: string; name: string; small?: boolean }) {
return (
<span
aria-label={name}
className={cn(
"inline-flex items-center justify-center rounded-full border border-border/80 bg-background font-mono font-semibold shadow-sm",
small ? "size-9 text-[10px]" : "size-11 text-xs"
)}
title={name}
>
{mark}
</span>
)
}
function SectionHeading({
eyebrow,
title,
description,
}: {
eyebrow: string
title: string
description: string
}) {
return (
<div className="border-b px-6 py-16 text-center sm:px-10 sm:py-20">
<div className="mx-auto max-w-2xl">
<p className="mb-4 font-mono text-[11px] font-medium uppercase tracking-[0.18em] text-blue-600 dark:text-blue-400">
{eyebrow}
</p>
<h2 className="text-balance text-3xl font-medium tracking-[-0.045em] sm:text-4xl md:text-[44px] md:leading-[1.05]">
{title}
</h2>
<p className="mx-auto mt-4 max-w-xl text-pretty text-sm leading-6 text-muted-foreground sm:text-base">
{description}
</p>
</div>
</div>
)
}
function FeatureCopy({
icon: Icon,
title,
description,
}: {
icon: React.ComponentType<{ className?: string }>
title: string
description: string
}) {
return (
<div className="relative z-10 border-t bg-background/85 p-6 backdrop-blur-sm sm:p-7">
<div className="mb-4 flex size-9 items-center justify-center rounded-lg border bg-muted/50 text-blue-600 dark:text-blue-400">
<Icon className="size-4" />
</div>
<h3 className="text-lg font-medium tracking-[-0.025em]">{title}</h3>
<p className="mt-2 max-w-md text-sm leading-6 text-muted-foreground">{description}</p>
</div>
)
}
function ReviewCard({ review }: { review: (typeof reviews)[number] }) {
return (
<figure className="h-full w-[310px] shrink-0 rounded-xl border bg-card/75 p-5 shadow-sm transition-colors hover:bg-muted/40 sm:w-[360px]">
<blockquote className="text-sm leading-6 text-foreground/85">“{review.quote}”</blockquote>
<figcaption className="mt-6 flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-full bg-blue-600/10 font-mono text-[11px] font-semibold text-blue-600 dark:text-blue-400">
{review.initials}
</span>
<span>
<span className="block text-sm font-medium">{review.name}</span>
<span className="block text-xs text-muted-foreground">{review.role}</span>
</span>
</figcaption>
</figure>
)
}
export function VertexLandingPage() {
const [annual, setAnnual] = useState(false)
const firstReviews = reviews.slice(0, 3)
const secondReviews = reviews.slice(3)
return (
<div className="min-h-screen bg-background text-foreground selection:bg-blue-600 selection:text-white">
<header className="sticky top-0 z-50 mx-auto max-w-[1280px] border-x border-b bg-background/82 backdrop-blur-xl supports-[backdrop-filter]:bg-background/72">
<div className="flex h-16 items-center justify-between px-5 sm:px-8">
<a href="#top" aria-label="Vertex home">
<Brand />
</a>
<nav aria-label="Main navigation" className="hidden items-center gap-1 rounded-full border bg-muted/40 p-1 md:flex">
{[
["Models", "#models"],
["Features", "#features"],
["Developers", "#developers"],
["Pricing", "#pricing"],
].map(([label, href]) => (
<a
key={label}
href={href}
className="rounded-full px-4 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-background hover:text-foreground"
>
{label}
</a>
))}
</nav>
<div className="flex items-center gap-2">
<a
href="#pricing"
className="hidden h-9 items-center gap-1.5 rounded-full bg-blue-600 px-4 text-xs font-medium text-white shadow-[0_6px_24px_rgba(37,99,235,.25)] transition hover:bg-blue-500 sm:inline-flex"
>
Open dashboard <ChevronRight className="size-3.5" />
</a>
<ThemeToggle />
</div>
</div>
</header>
<main id="top" className="mx-auto max-w-[1280px] border-x">
<section className="vertex-grid relative overflow-hidden border-b px-6 pb-14 pt-24 text-center sm:px-10 sm:pb-20 sm:pt-28 lg:pt-32">
<div className="pointer-events-none absolute inset-x-0 top-0 h-56 bg-gradient-to-b from-blue-600/[0.07] to-transparent" />
<div className="relative z-10 mx-auto max-w-4xl">
<a
href="#features"
className="mb-7 inline-flex items-center gap-2 rounded-full border bg-background/75 px-3.5 py-1.5 text-xs text-muted-foreground shadow-sm backdrop-blur"
>
<Sparkles className="size-3.5 text-blue-600 dark:text-blue-400" />
The unified AI gateway
<ArrowRight className="size-3.5" />
</a>
<h1 className="text-balance text-[44px] font-medium leading-[0.98] tracking-[-0.06em] sm:text-6xl md:text-7xl lg:text-[82px]">
One API for every
<span className="block bg-gradient-to-r from-blue-600 via-blue-500 to-violet-500 bg-clip-text text-transparent">
AI model.
</span>
</h1>
<p className="mx-auto mt-6 max-w-2xl text-pretty text-base leading-7 text-muted-foreground sm:text-lg">
Route, optimize, and observe every model request through one OpenAI-compatible endpoint built for production.
</p>
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<a
href="#pricing"
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-full bg-blue-600 px-6 text-sm font-medium text-white shadow-[0_10px_32px_rgba(37,99,235,.28)] transition hover:-translate-y-0.5 hover:bg-blue-500 sm:w-auto"
>
Get your API key <ArrowRight className="size-4" />
</a>
<a
href="#models"
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-full border bg-background/70 px-6 text-sm font-medium transition hover:bg-muted sm:w-auto"
>
Explore models
</a>
</div>
</div>
<div className="relative z-10 mx-auto mt-16 max-w-4xl overflow-hidden rounded-2xl border bg-background/80 p-4 shadow-[0_32px_90px_rgba(0,0,0,.08)] backdrop-blur sm:p-6 dark:shadow-[0_32px_90px_rgba(0,0,0,.35)]">
<div className="absolute inset-0 bg-[linear-gradient(to_right,var(--border)_1px,transparent_1px),linear-gradient(to_bottom,var(--border)_1px,transparent_1px)] bg-[size:32px_32px] opacity-35" />
<div className="relative grid items-center gap-4 sm:grid-cols-[1fr_auto_1fr_auto_1fr]">
<div className="rounded-xl border bg-background p-4 text-left shadow-sm">
<div className="flex items-center gap-2 text-xs font-medium">
<Code2 className="size-4 text-blue-600" /> Your application
</div>
<code className="mt-3 block truncate rounded-md bg-muted px-2.5 py-2 font-mono text-[10px] text-muted-foreground sm:text-xs">
POST /v1/chat/completions
</code>
</div>
<ArrowRight className="mx-auto hidden size-4 text-muted-foreground sm:block" />
<div className="relative rounded-xl border border-blue-500/30 bg-blue-600 p-4 text-left text-white shadow-[0_10px_40px_rgba(37,99,235,.25)]">
<div className="flex items-center gap-2 text-xs font-medium">
<Waypoints className="size-4" /> Vertex Gateway
</div>
<div className="mt-3 flex items-center gap-2 font-mono text-[10px] text-blue-100 sm:text-xs">
<span className="size-1.5 animate-pulse rounded-full bg-emerald-300" /> Routing by latency
</div>
</div>
<ArrowRight className="mx-auto hidden size-4 text-muted-foreground sm:block" />
<div className="rounded-xl border bg-background p-4 text-left shadow-sm">
<div className="flex items-center gap-2 text-xs font-medium">
<Network className="size-4 text-violet-500" /> Best available model
</div>
<div className="mt-3 flex -space-x-2">
{providers.slice(0, 4).map((provider) => (
<ProviderMark key={provider.name} {...provider} small />
))}
</div>
</div>
</div>
</div>
</section>
<section id="models" className="border-b">
<p className="border-b py-7 text-center text-sm text-muted-foreground">
Every leading model, one gateway
</p>
<div className="grid grid-cols-2 sm:grid-cols-4">
{providers.map((provider) => (
<div
key={provider.name}
className="group flex h-28 items-center justify-center gap-3 border-r border-b last:border-r-0 even:border-r-0 sm:even:border-r sm:[&:nth-child(4n)]:border-r-0 [&:nth-last-child(-n+2)]:border-b-0 sm:[&:nth-last-child(-n+4)]:border-b-0"
>
<ProviderMark {...provider} />
<span className="text-sm font-medium tracking-tight text-muted-foreground transition-colors group-hover:text-foreground sm:text-base">
{provider.name}
</span>
</div>
))}
</div>
</section>
<section className="grid border-b sm:grid-cols-[56px_1fr_56px]">
<div className="hatched hidden border-r sm:block" aria-hidden="true" />
<div className="px-6 py-16 text-center sm:px-12 sm:py-20">
<h2 className="text-balance text-3xl font-medium tracking-[-0.045em] sm:text-4xl">
Switch models without rewriting your stack.
</h2>
<p className="mx-auto mt-4 max-w-2xl text-pretty text-sm leading-6 text-muted-foreground sm:text-base">
One stable API gives your team the freedom to change providers, enforce policies, and optimize every request behind the scenes.
</p>
</div>
<div className="hatched hidden border-l sm:block" aria-hidden="true" />
</section>
<section id="features" className="border-b px-4 py-4 sm:px-8 sm:py-8">
<div className="border-x border-t">
<SectionHeading
eyebrow="The gateway layer"
title="Infrastructure for model choice"
description="Everything you need to move quickly across models without giving up reliability, control, or visibility."
/>
<div className="grid lg:grid-cols-12">
<article className="flex min-h-[520px] flex-col border-b lg:col-span-7 lg:border-r">
<div className="flex flex-1 items-center justify-center overflow-hidden bg-muted/20 p-5 sm:p-10">
<Terminal className="max-w-xl bg-background shadow-[0_24px_70px_rgba(0,0,0,.12)]" sequence>
<TypingAnimation duration={22}>
{"> curl https://api.vertex.ai/v1/chat/completions \\"}
</TypingAnimation>
<AnimatedSpan className="text-muted-foreground">
{` -d '{ "model": "vertex/auto", "messages": [...] }'`}
</AnimatedSpan>
<AnimatedSpan className="text-blue-500">
→ evaluating 14 available models
</AnimatedSpan>
<AnimatedSpan className="text-violet-500">
→ route selected: anthropic/claude-sonnet
</AnimatedSpan>
<AnimatedSpan className="text-emerald-500">
✓ response streamed in 342ms
</AnimatedSpan>
<TypingAnimation duration={18} className="text-muted-foreground">
One endpoint. Any model. No rewrites.
</TypingAnimation>
</Terminal>
</div>
<FeatureCopy
icon={Code2}
title="One compatible endpoint"
description="Use the OpenAI SDK you already know. Change one base URL and reach every supported model."
/>
</article>
<article className="flex min-h-[520px] flex-col border-b lg:col-span-5">
<div className="relative flex flex-1 items-center justify-center overflow-hidden bg-muted/20">
<div className="absolute left-1/2 top-1/2 z-10 flex size-20 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-2xl bg-blue-600 text-white shadow-[0_12px_50px_rgba(37,99,235,.4)]">
<Waypoints className="size-8" />
</div>
<OrbitingCircles radius={84} iconSize={38} duration={16}>
{providers.slice(0, 4).map((provider) => (
<ProviderMark key={provider.name} {...provider} small />
))}
</OrbitingCircles>
<OrbitingCircles radius={150} iconSize={38} duration={24} reverse>
{providers.slice(4).map((provider) => (
<ProviderMark key={provider.name} {...provider} small />
))}
</OrbitingCircles>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_15%,var(--background)_74%)] opacity-55" />
</div>
<FeatureCopy
icon={Route}
title="Intelligent model routing"
description="Route by price, latency, capability, or your own policy—with automatic provider fallbacks."
/>
</article>
<article className="flex min-h-[430px] flex-col border-b lg:col-span-4 lg:border-b-0 lg:border-r">
<div className="relative h-[270px] flex-1 overflow-hidden bg-muted/20 p-4">
<AnimatedList delay={1150} className="gap-2">
{routingEvents.map((event) => (
<div key={event.title} className="mx-auto flex w-full max-w-sm items-center gap-3 rounded-xl border bg-background/95 p-3 shadow-sm">
<span className={cn("flex size-9 shrink-0 items-center justify-center rounded-lg", event.accent)}>
{event.icon}
</span>
<span className="min-w-0 text-left">
<span className="block truncate text-xs font-medium">{event.title}</span>
<span className="mt-0.5 block truncate font-mono text-[10px] text-muted-foreground">{event.meta}</span>
</span>
</div>
))}
</AnimatedList>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-background to-transparent" />
</div>
<FeatureCopy
icon={ShieldCheck}
title="Automatic failover"
description="Keep requests moving through rate limits, outages, and changing provider availability."
/>
</article>
<article className="flex min-h-[430px] flex-col border-b lg:col-span-4 lg:border-b-0 lg:border-r">
<div className="flex h-[270px] flex-1 items-center bg-muted/20 px-4 pt-5">
<ChartContainer config={chartConfig} className="h-[220px] w-full">
<AreaChart data={usageData} margin={{ left: -24, right: 8, top: 12, bottom: 0 }}>
<defs>
<linearGradient id="fillCost" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-cost)" stopOpacity={0.45} />
<stop offset="95%" stopColor="var(--color-cost)" stopOpacity={0} />
</linearGradient>
<linearGradient id="fillLatency" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-latency)" stopOpacity={0.26} />
<stop offset="95%" stopColor="var(--color-latency)" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis dataKey="time" axisLine={false} tickLine={false} tickMargin={8} />
<YAxis hide domain={[0, 100]} />
<ChartTooltip content={<ChartTooltipContent indicator="line" />} />
<Area dataKey="latency" type="monotone" fill="url(#fillLatency)" stroke="var(--color-latency)" strokeWidth={1.5} />
<Area dataKey="cost" type="monotone" fill="url(#fillCost)" stroke="var(--color-cost)" strokeWidth={2} />
</AreaChart>
</ChartContainer>
</div>
<FeatureCopy
icon={Gauge}
title="Unified cost and latency"
description="Compare providers, trace every request, and control spend from one clear operating view."
/>
</article>
<article className="flex min-h-[430px] flex-col lg:col-span-4">
<div className="relative h-[270px] flex-1 overflow-hidden bg-muted/20">
<Globe className="top-6 max-w-[360px]" config={globeConfig} />
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_105%,rgba(37,99,235,.18),transparent_58%)]" />
<div className="absolute left-5 top-5 flex items-center gap-2 rounded-full border bg-background/85 px-3 py-1.5 font-mono text-[10px] shadow-sm backdrop-blur">
<span className="size-1.5 animate-pulse rounded-full bg-emerald-500" /> 8 regions online
</div>
</div>
<FeatureCopy
icon={Globe2}
title="Global inference gateway"
description="Route requests through the nearest healthy region for consistently fast model access."
/>
</article>
</div>
</div>
</section>
<section id="developers" className="border-b">
<SectionHeading
eyebrow="Start in minutes"
title="A simpler path to production AI"
description="Your application stays focused on the product. Vertex handles the shifting model landscape underneath."
/>
<div className="grid md:grid-cols-3">
{[
{
number: "01",
icon: Code2,
title: "Change one base URL",
text: "Keep your existing OpenAI-compatible SDK, payloads, and streaming logic.",
},
{
number: "02",
icon: Workflow,
title: "Set a routing policy",
text: "Choose a model directly or let Vertex optimize for cost, quality, or latency.",
},
{
number: "03",
icon: Gauge,
title: "Observe every request",
text: "Trace performance and spend across models, applications, and environments.",
},
].map((step, index) => (
<article key={step.number} className={cn("relative p-8 sm:p-10", index < 2 && "border-b md:border-b-0 md:border-r")}>
<span className="font-mono text-xs text-blue-600 dark:text-blue-400">{step.number}</span>
<step.icon className="mt-10 size-6" />
<h3 className="mt-5 text-lg font-medium tracking-tight">{step.title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{step.text}</p>
</article>
))}
</div>
</section>
<section className="relative overflow-hidden border-b px-6 py-20 text-center sm:px-12 sm:py-24">
<Ripple className="opacity-40" mainCircleSize={180} numCircles={9} />
<div className="relative z-10 mx-auto max-w-3xl">
<div className="mx-auto flex size-12 items-center justify-center rounded-2xl border bg-background shadow-sm">
<LockKeyhole className="size-5 text-blue-600" />
</div>
<h2 className="mt-6 text-balance text-3xl font-medium tracking-[-0.045em] sm:text-4xl">
Reliability built into every request.
</h2>
<p className="mx-auto mt-4 max-w-xl text-sm leading-6 text-muted-foreground sm:text-base">
Production-grade controls keep your workloads available, observable, and protected as they scale.
</p>
<div className="mt-10 grid grid-cols-3 overflow-hidden rounded-2xl border bg-background/80 backdrop-blur">
{[
["99.99%", "Gateway uptime"],
["8", "Global regions"],
["< 20ms", "Routing overhead"],
].map(([value, label], index) => (
<div key={label} className={cn("px-2 py-5 sm:px-6", index < 2 && "border-r")}>
<strong className="block text-lg font-medium tracking-tight sm:text-2xl">{value}</strong>
<span className="mt-1 block text-[10px] text-muted-foreground sm:text-xs">{label}</span>
</div>
))}
</div>
</div>
</section>
<section id="pricing" className="border-b">
<SectionHeading
eyebrow="Simple pricing"
title="Start free. Scale without surprises."
description="Pay for the models you use and add platform controls when your team needs them."
/>
<div className="flex justify-center border-b py-6">
<div className="inline-flex rounded-full border bg-muted/40 p-1" aria-label="Billing cadence">
<button
type="button"
aria-pressed={!annual}
onClick={() => setAnnual(false)}
className={cn("rounded-full px-4 py-1.5 text-xs font-medium transition", !annual ? "bg-background text-foreground shadow-sm" : "text-muted-foreground")}
>
Monthly
</button>
<button
type="button"
aria-pressed={annual}
onClick={() => setAnnual(true)}
className={cn("rounded-full px-4 py-1.5 text-xs font-medium transition", annual ? "bg-background text-foreground shadow-sm" : "text-muted-foreground")}
>
Annual <span className="text-blue-600 dark:text-blue-400">−20%</span>
</button>
</div>
</div>
<div className="grid gap-px bg-border lg:grid-cols-3">
{[
{
name: "Developer",
price: "$0",
cadence: "/ month",
description: "For prototypes, side projects, and individual builders.",
action: "Start building",
popular: false,
features: ["Pay-as-you-go model usage", "All model providers", "Basic usage analytics", "Community support"],
},
{
name: "Team",
price: annual ? "$39" : "$49",
cadence: "/ month",
description: "For teams running reliable AI products in production.",
action: "Start 14-day trial",
popular: true,
features: ["Everything in Developer", "Routing and fallback policies", "Team budgets and alerts", "90-day request analytics", "Priority support"],
},
{
name: "Enterprise",
price: "Custom",
cadence: "",
description: "For organizations that need security and scale.",
action: "Talk to sales",
popular: false,
features: ["Everything in Team", "SAML SSO and audit logs", "Custom retention and regions", "Dedicated capacity", "Enterprise SLA"],
},
].map((plan) => (
<article key={plan.name} className={cn("relative flex flex-col bg-background p-7 sm:p-9", plan.popular && "bg-blue-600/[0.035]")}>
{plan.popular && (
<span className="absolute right-6 top-7 rounded-full bg-blue-600 px-2.5 py-1 font-mono text-[9px] font-semibold uppercase tracking-wider text-white">
Most popular
</span>
)}
<p className="text-sm font-medium">{plan.name}</p>
<div className="mt-7 flex items-end gap-2">
<span className="text-4xl font-medium tracking-[-0.05em]">{plan.price}</span>
{plan.cadence && <span className="pb-1 text-xs text-muted-foreground">{plan.cadence}</span>}
</div>
<p className="mt-4 min-h-12 text-sm leading-6 text-muted-foreground">{plan.description}</p>
<a
href="#top"
className={cn(
"mt-7 inline-flex h-10 items-center justify-center rounded-full border text-sm font-medium transition",
plan.popular ? "border-blue-600 bg-blue-600 text-white hover:bg-blue-500" : "bg-background hover:bg-muted"
)}
>
{plan.action}
</a>
<div className="my-7 border-t" />
<ul className="space-y-3">
{plan.features.map((feature) => (
<li key={feature} className="flex items-start gap-2.5 text-sm text-muted-foreground">
<Check className="mt-0.5 size-4 shrink-0 text-blue-600 dark:text-blue-400" />
{feature}
</li>
))}
</ul>
</article>
))}
</div>
<p className="border-t px-6 py-5 text-center text-xs text-muted-foreground">
Model usage is billed separately at transparent provider rates. No long-term contracts.
</p>
</section>
<section className="border-b py-16 sm:py-20">
<div className="px-6 text-center">
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-blue-600 dark:text-blue-400">Early access feedback</p>
<h2 className="mt-4 text-3xl font-medium tracking-[-0.045em] sm:text-4xl">Built for teams shipping AI</h2>
</div>
<div className="relative mt-12 overflow-hidden">
<Marquee pauseOnHover className="[--duration:34s] [--gap:1rem]">
{firstReviews.map((review) => <ReviewCard key={review.name} review={review} />)}
</Marquee>
<Marquee reverse pauseOnHover className="mt-2 [--duration:38s] [--gap:1rem]">
{secondReviews.map((review) => <ReviewCard key={review.name} review={review} />)}
</Marquee>
<div className="pointer-events-none absolute inset-y-0 left-0 w-20 bg-gradient-to-r from-background to-transparent sm:w-40" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-20 bg-gradient-to-l from-background to-transparent sm:w-40" />
</div>
</section>
<section className="grid border-b sm:grid-cols-[56px_1fr_56px]">
<div className="hatched hidden border-r sm:block" aria-hidden="true" />
<div className="relative overflow-hidden px-6 py-24 text-center sm:px-10 sm:py-28">
<FlickeringGrid
className="absolute inset-0 [mask-image:radial-gradient(420px_circle_at_center,white,transparent)]"
squareSize={3}
gridGap={7}
color="#2563eb"
maxOpacity={0.42}
flickerChance={0.08}
/>
<div className="relative z-10 mx-auto max-w-3xl">
<div className="mx-auto mb-7 flex size-12 items-center justify-center rounded-2xl bg-blue-600 text-white shadow-[0_10px_40px_rgba(37,99,235,.35)]">
<Waypoints className="size-6" />
</div>
<h2 className="text-balance text-4xl font-medium tracking-[-0.055em] sm:text-5xl md:text-6xl">
Build on every model.
<span className="block text-blue-600 dark:text-blue-400">Ship through one API.</span>
</h2>
<p className="mx-auto mt-5 max-w-lg text-sm leading-6 text-muted-foreground sm:text-base">
Your models will change. Your infrastructure doesn’t have to.
</p>
</div>
</div>
<div className="hatched hidden border-l sm:block" aria-hidden="true" />
</section>
<footer className="px-6 py-10 sm:px-10">
<div className="flex flex-col gap-8 sm:flex-row sm:items-center sm:justify-between">
<div>
<Brand />
<p className="mt-3 max-w-sm text-xs leading-5 text-muted-foreground">
The unified gateway for production AI applications.
</p>
</div>
<div className="flex flex-wrap gap-x-6 gap-y-3 text-xs text-muted-foreground">
<a className="hover:text-foreground" href="#models">Models</a>
<a className="hover:text-foreground" href="#developers">Docs</a>
<a className="hover:text-foreground" href="#pricing">Pricing</a>
<a className="hover:text-foreground" href="#top">Status</a>
<a className="hover:text-foreground" href="#top">Privacy</a>
</div>
</div>
<div className="mt-10 flex flex-col gap-2 border-t pt-6 text-[11px] text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
<span>© 2026 Vertex, Inc. All rights reserved.</span>
<span className="inline-flex items-center gap-1.5"><span className="size-1.5 rounded-full bg-emerald-500" /> All systems operational</span>
</div>
</footer>
</main>
</div>
)
}