Template
Organize Vertex starter into reusable sections
Use remote logos and avatars, refine landing copy, and document the template structure.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { Brand } from "@/components/shared/brand"
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<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 text-[11px] text-muted-foreground">
|
||||
<span>© 2026 Vertex, Inc. All rights reserved.</span>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ChevronDown, ChevronRight } from "lucide-react"
|
||||
|
||||
import { ThemeToggle } from "@/components/layout/theme-toggle"
|
||||
import { Brand } from "@/components/shared/brand"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/86 backdrop-blur-xl supports-[backdrop-filter]:bg-background/74">
|
||||
<div className="mx-auto flex h-16 max-w-[1280px] 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-8 md:flex lg:gap-10">
|
||||
{[
|
||||
["Products", "#features"],
|
||||
["Models", "#models"],
|
||||
["Features", "#features"],
|
||||
["Pricing", "#pricing"],
|
||||
["Docs", "#developers"],
|
||||
].map(([label, href]) => (
|
||||
<a
|
||||
key={label}
|
||||
href={href}
|
||||
className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
{label}
|
||||
{label === "Products" && <ChevronDown className="size-3" />}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<a
|
||||
href="#pricing"
|
||||
className="hidden h-9 items-center gap-1.5 rounded-sm bg-blue-600 px-4 text-xs font-medium text-white shadow-[0_6px_24px_rgba(23,61,166,.28)] transition hover:bg-blue-500 sm:inline-flex"
|
||||
>
|
||||
Get started <ChevronRight className="size-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { VertexMark } from "@/components/shared/brand"
|
||||
import { FrameCrosses } from "@/components/shared/frame-crosses"
|
||||
import { FlickeringGrid } from "@/components/ui/flickering-grid"
|
||||
|
||||
export function CtaSection() {
|
||||
return (
|
||||
<section className="relative border-b">
|
||||
<FrameCrosses />
|
||||
<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="#173DA6"
|
||||
maxOpacity={0.42}
|
||||
flickerChance={0.08}
|
||||
/>
|
||||
<div className="relative z-10 mx-auto max-w-3xl">
|
||||
<VertexMark className="mx-auto mb-7 size-12" />
|
||||
<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>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Code2, Gauge, Workflow } from "lucide-react"
|
||||
|
||||
import { SectionHeading } from "@/components/shared/section-heading"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function DevelopersSection() {
|
||||
return (
|
||||
<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">
|
||||
{[
|
||||
{
|
||||
icon: Code2,
|
||||
title: "Change one base URL",
|
||||
text: "Keep your existing OpenAI-compatible SDK, payloads, and streaming logic.",
|
||||
},
|
||||
{
|
||||
icon: Workflow,
|
||||
title: "Set a routing policy",
|
||||
text: "Choose a model directly or let Vertex optimize for cost, quality, or latency.",
|
||||
},
|
||||
{
|
||||
icon: Gauge,
|
||||
title: "Observe every request",
|
||||
text: "Trace performance and spend across models, applications, and environments.",
|
||||
},
|
||||
].map((step, index) => (
|
||||
<article key={step.title} className={cn("relative p-8 sm:p-10", index < 2 && "border-b md:border-b-0 md:border-r")}>
|
||||
<step.icon className="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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ApiTerminal } from "@/components/sections/features/api-terminal"
|
||||
import { GlobalGlobe } from "@/components/sections/features/global-globe"
|
||||
import { IntegrationOrbit } from "@/components/sections/features/integration-orbit"
|
||||
import { RoutingActivity } from "@/components/sections/features/routing-activity"
|
||||
import { UsageChart } from "@/components/sections/features/usage-chart"
|
||||
import { FeatureCopy } from "@/components/shared/feature-copy"
|
||||
import { SectionHeading } from "@/components/shared/section-heading"
|
||||
|
||||
export function FeaturesSection() {
|
||||
return (
|
||||
<section id="features" className="border-b">
|
||||
<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">
|
||||
<ApiTerminal />
|
||||
<FeatureCopy
|
||||
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">
|
||||
<IntegrationOrbit />
|
||||
<FeatureCopy
|
||||
title="Seamless integrations"
|
||||
description="Connect your favorite tools and bring AI into the workflows your team already uses."
|
||||
/>
|
||||
</article>
|
||||
|
||||
<article className="flex min-h-[430px] flex-col border-b lg:col-span-4 lg:border-b-0 lg:border-r">
|
||||
<RoutingActivity />
|
||||
<FeatureCopy
|
||||
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="relative h-[360px] shrink-0 overflow-hidden bg-background">
|
||||
<UsageChart />
|
||||
</div>
|
||||
<FeatureCopy
|
||||
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">
|
||||
<GlobalGlobe />
|
||||
<FeatureCopy
|
||||
title="Global inference gateway"
|
||||
description="Route requests through the nearest healthy region for consistently fast model access."
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
AnimatedSpan,
|
||||
Terminal,
|
||||
TypingAnimation,
|
||||
} from "@/components/ui/terminal"
|
||||
|
||||
export function ApiTerminal() {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center overflow-hidden bg-background 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-blue-600 dark:text-blue-400">
|
||||
→ 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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
"use client"
|
||||
|
||||
import { Globe } from "@/components/ui/globe"
|
||||
|
||||
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 },
|
||||
],
|
||||
}
|
||||
|
||||
export function GlobalGlobe() {
|
||||
return (
|
||||
<div className="relative h-[360px] shrink-0 overflow-hidden bg-background">
|
||||
<Globe className="top-6 max-w-[360px]" config={globeConfig} />
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { VertexMark } from "@/components/shared/brand"
|
||||
import { OrbitingCircles } from "@/components/ui/orbiting-circles"
|
||||
|
||||
const integrations = [
|
||||
{
|
||||
name: "Supabase",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/supabase.svg",
|
||||
},
|
||||
{
|
||||
name: "Notion",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/notion.svg",
|
||||
},
|
||||
{
|
||||
name: "Figma",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/figma.svg",
|
||||
},
|
||||
{
|
||||
name: "Dropbox",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/dropbox.svg",
|
||||
},
|
||||
{
|
||||
name: "Google",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/google.svg",
|
||||
},
|
||||
{
|
||||
name: "OpenAI",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/openai.svg",
|
||||
},
|
||||
{
|
||||
name: "Stripe",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/stripe.svg",
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/square/github.svg",
|
||||
},
|
||||
]
|
||||
|
||||
function IntegrationMark({ src, name }: { src: string; name: string }) {
|
||||
return (
|
||||
<span
|
||||
className="inline-flex size-full items-center justify-center rounded-full border border-black/10 bg-white p-2 shadow-sm"
|
||||
title={name}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
alt={`${name} logo`}
|
||||
width={28}
|
||||
height={28}
|
||||
unoptimized
|
||||
className="size-full object-contain"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function IntegrationOrbit() {
|
||||
return (
|
||||
<div className="relative flex flex-1 items-center justify-center overflow-hidden bg-background">
|
||||
<VertexMark className="absolute left-1/2 top-1/2 z-10 size-12 -translate-x-1/2 -translate-y-1/2" />
|
||||
<OrbitingCircles className="z-10" radius={84} iconSize={44} duration={16}>
|
||||
{integrations.slice(0, 4).map((integration) => (
|
||||
<IntegrationMark key={integration.name} {...integration} />
|
||||
))}
|
||||
</OrbitingCircles>
|
||||
<OrbitingCircles className="z-10" radius={150} iconSize={44} duration={24} reverse>
|
||||
{integrations.slice(4).map((integration) => (
|
||||
<IntegrationMark key={integration.name} {...integration} />
|
||||
))}
|
||||
</OrbitingCircles>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-32 bg-gradient-to-t from-background via-background/80 to-transparent"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
"use client"
|
||||
|
||||
import { Activity, CircleCheck, Route, ShieldCheck, Zap } from "lucide-react"
|
||||
|
||||
import { AnimatedList } from "@/components/ui/animated-list"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const routingEvents = [
|
||||
{
|
||||
icon: <Route className="size-4" />,
|
||||
title: "Routed to Claude Sonnet",
|
||||
meta: "342ms · 8.2k tokens",
|
||||
accent: "bg-blue-600/15 text-blue-600 dark:text-blue-400",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
]
|
||||
|
||||
export function RoutingActivity() {
|
||||
return (
|
||||
<div className="relative h-[360px] shrink-0 overflow-hidden bg-background">
|
||||
<AnimatedList delay={1150} className="absolute inset-x-4 top-4 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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ArrowRight } from "lucide-react"
|
||||
|
||||
import { FrameCrosses } from "@/components/shared/frame-crosses"
|
||||
|
||||
export function HeroSection() {
|
||||
return (
|
||||
<section className="hero-gradient relative border-b px-6 pb-14 pt-24 text-center sm:px-10 sm:pb-20 sm:pt-28 lg:pt-32">
|
||||
<FrameCrosses />
|
||||
<div className="relative z-10 mx-auto max-w-4xl">
|
||||
<a
|
||||
href="#features"
|
||||
className="mb-7 inline-flex items-center justify-center rounded-full border border-blue-600/15 bg-gradient-to-br from-white/80 via-blue-600/10 to-blue-600/20 px-5 py-2 text-xs font-medium text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,.6),0_4px_20px_rgba(23,61,166,.1)] backdrop-blur-xl transition-colors hover:border-blue-600/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:border-white/15 dark:from-white/10 dark:via-blue-600/15 dark:to-blue-600/30 dark:shadow-[inset_0_1px_0_rgba(255,255,255,.12),0_4px_20px_rgba(23,61,166,.15)] dark:hover:border-white/30"
|
||||
>
|
||||
The unified AI gateway
|
||||
</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.
|
||||
<span className="block text-foreground/80">Every 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-sm bg-blue-600 px-6 text-sm font-medium text-white shadow-[0_10px_32px_rgba(23,61,166,.32)] 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-sm border bg-background/70 px-6 text-sm font-medium transition hover:bg-muted sm:w-auto"
|
||||
>
|
||||
Explore models
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { FlickeringGrid } from "@/components/ui/flickering-grid"
|
||||
|
||||
const customerLogos = [
|
||||
{
|
||||
name: "Next.js",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/nextjs-logo.svg",
|
||||
width: 394,
|
||||
height: 80,
|
||||
},
|
||||
{
|
||||
name: "OpenAI",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/openai-logo.svg",
|
||||
width: 512,
|
||||
height: 126,
|
||||
},
|
||||
{
|
||||
name: "Stripe",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/stripe-logo.svg",
|
||||
width: 73,
|
||||
height: 31,
|
||||
},
|
||||
{
|
||||
name: "Vercel",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/vercel-logo.svg",
|
||||
width: 2048,
|
||||
height: 407,
|
||||
},
|
||||
{
|
||||
name: "Supabase",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/supabase-logo.svg",
|
||||
width: 581,
|
||||
height: 113,
|
||||
},
|
||||
{
|
||||
name: "Anthropic",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/anthropic-logo.svg",
|
||||
width: 579,
|
||||
height: 65,
|
||||
},
|
||||
{
|
||||
name: "Tailwind CSS",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/tailwindcss-logo.svg",
|
||||
width: 665,
|
||||
height: 82,
|
||||
},
|
||||
{
|
||||
name: "Linear",
|
||||
src: "https://sb.frontend.co/storage/v1/object/public/assets/logos/standard/linear-logo.svg",
|
||||
width: 122,
|
||||
height: 31,
|
||||
},
|
||||
]
|
||||
|
||||
export function LogosSection() {
|
||||
return (
|
||||
<section id="models" className="border-b">
|
||||
<div className="relative flex min-h-[260px] items-center justify-center overflow-hidden border-b px-6 py-20 text-center sm:min-h-[340px] sm:py-28">
|
||||
<FlickeringGrid
|
||||
className="absolute inset-0 [mask-image:radial-gradient(520px_circle_at_center,white,transparent)]"
|
||||
squareSize={3}
|
||||
gridGap={8}
|
||||
color="#173DA6"
|
||||
maxOpacity={0.34}
|
||||
flickerChance={0.08}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_12%,var(--background)_78%)] opacity-70" />
|
||||
<h2 className="relative z-10 max-w-3xl text-balance text-4xl font-medium leading-[1.02] tracking-[-0.05em] sm:text-5xl md:text-6xl">
|
||||
Built for
|
||||
<span className="block text-blue-600 dark:text-blue-400">your AI stack.</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-px bg-border sm:grid-cols-4">
|
||||
{customerLogos.map((logo) => (
|
||||
<div
|
||||
key={logo.name}
|
||||
className="group flex h-32 items-center justify-center bg-background px-6 sm:h-36"
|
||||
>
|
||||
<Image
|
||||
src={logo.src}
|
||||
alt={`${logo.name} logo`}
|
||||
width={logo.width}
|
||||
height={logo.height}
|
||||
unoptimized
|
||||
className="h-8 w-[132px] max-w-full object-contain opacity-75 brightness-0 transition-all duration-200 group-hover:scale-[1.03] group-hover:opacity-100 dark:invert"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
import { SectionHeading } from "@/components/shared/section-heading"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function PricingSection() {
|
||||
const [annual, setAnnual] = useState(false)
|
||||
|
||||
return (
|
||||
<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."
|
||||
className="border-b-0"
|
||||
/>
|
||||
<div className="flex justify-center border-b py-6">
|
||||
<div className="inline-flex rounded-sm border bg-muted/40 p-1" aria-label="Billing cadence">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={!annual}
|
||||
onClick={() => setAnnual(false)}
|
||||
className={cn("rounded-sm 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-sm 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", plan.popular && "pricing-card-popular")}>
|
||||
<div className="relative z-10 flex flex-1 flex-col p-7 sm:p-9">
|
||||
<div className="flex min-h-7 flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-sm font-medium">{plan.name}</p>
|
||||
{plan.popular && (
|
||||
<span className="rounded-full border border-blue-600/40 px-3 py-1 text-xs font-medium text-blue-600 dark:border-[#5479db]/40 dark:text-blue-400">
|
||||
Most popular
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<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-sm 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>
|
||||
<ul className="mt-7 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>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { LockKeyhole } from "lucide-react"
|
||||
|
||||
import { Ripple } from "@/components/ui/ripple"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function ReliabilitySection() {
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ReviewCard, type Testimonial } from "@/components/shared/review-card"
|
||||
import { Marquee } from "@/components/ui/marquee"
|
||||
|
||||
const reviews: Testimonial[] = [
|
||||
{
|
||||
avatar: "https://sb.frontend.co/storage/v1/object/public/assets/avatars/unsplash/female-1.jpeg",
|
||||
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.",
|
||||
},
|
||||
{
|
||||
avatar: "https://sb.frontend.co/storage/v1/object/public/assets/avatars/unsplash/male-1.jpeg",
|
||||
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.",
|
||||
},
|
||||
{
|
||||
avatar: "https://sb.frontend.co/storage/v1/object/public/assets/avatars/unsplash/male-2.jpeg",
|
||||
name: "Ari Rao",
|
||||
role: "VP of Engineering",
|
||||
quote:
|
||||
"The unified cost view finally made inference spend understandable across teams, providers, and environments.",
|
||||
},
|
||||
{
|
||||
avatar: "https://sb.frontend.co/storage/v1/object/public/assets/avatars/unsplash/female-2.jpeg",
|
||||
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.",
|
||||
},
|
||||
{
|
||||
avatar: "https://sb.frontend.co/storage/v1/object/public/assets/avatars/unsplash/male-3.jpeg",
|
||||
name: "Owen Taylor",
|
||||
role: "Infrastructure Director",
|
||||
quote:
|
||||
"Vertex lets our developers move quickly while the platform team keeps control of budgets, policies, and reliability.",
|
||||
},
|
||||
{
|
||||
avatar: "https://sb.frontend.co/storage/v1/object/public/assets/avatars/unsplash/female-3.jpeg",
|
||||
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.",
|
||||
},
|
||||
]
|
||||
|
||||
export function TestimonialsSection() {
|
||||
const firstReviews = reviews.slice(0, 3)
|
||||
const secondReviews = reviews.slice(3)
|
||||
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function VertexMark({ className }: { className?: string }) {
|
||||
return (
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className={cn("brightness-0 dark:invert", className)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Brand({ compact = false }: { compact?: boolean }) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-2.5">
|
||||
<VertexMark className="size-8" />
|
||||
{!compact && (
|
||||
<span className="text-[17px] font-semibold tracking-[-0.03em]">Vertex</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export function FeatureCopy({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
}) {
|
||||
return (
|
||||
<div className="relative z-10 bg-background p-6 sm:p-7">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export function FrameCrosses() {
|
||||
return (
|
||||
<>
|
||||
<span className="frame-cross left-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross right-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 left-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 right-0" aria-hidden="true" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import Image from "next/image"
|
||||
|
||||
export interface Testimonial {
|
||||
avatar: string
|
||||
name: string
|
||||
role: string
|
||||
quote: string
|
||||
}
|
||||
|
||||
export function ReviewCard({ review }: { review: Testimonial }) {
|
||||
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">
|
||||
<Image
|
||||
src={review.avatar}
|
||||
alt=""
|
||||
width={36}
|
||||
height={36}
|
||||
sizes="36px"
|
||||
unoptimized
|
||||
className="size-9 shrink-0 rounded-full border object-cover"
|
||||
/>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function SectionHeading({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
className,
|
||||
}: {
|
||||
eyebrow: string
|
||||
title: string
|
||||
description: string
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("border-b px-6 py-16 text-center sm:px-10 sm:py-20", className)}>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -1,708 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import Image from "next/image"
|
||||
import { useState } from "react"
|
||||
import {
|
||||
Activity,
|
||||
ArrowRight,
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
CircleCheck,
|
||||
Code2,
|
||||
Gauge,
|
||||
LockKeyhole,
|
||||
Route,
|
||||
ShieldCheck,
|
||||
Workflow,
|
||||
Zap,
|
||||
} from "lucide-react"
|
||||
import { ThemeToggle } from "@/components/theme-toggle"
|
||||
import { UsageChart } from "@/components/usage-chart"
|
||||
import { AnimatedList } from "@/components/ui/animated-list"
|
||||
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"
|
||||
|
||||
// Original SVG artwork sourced from https://svgl.app.
|
||||
const integrations = [
|
||||
{ name: "Supabase", src: "/logos/integrations/supabase.svg" },
|
||||
{ name: "Notion", src: "/logos/integrations/notion.svg" },
|
||||
{ name: "Figma", src: "/logos/integrations/figma.svg" },
|
||||
{ name: "Dropbox", src: "/logos/integrations/dropbox.svg" },
|
||||
{ name: "Gmail", src: "/logos/integrations/gmail.svg" },
|
||||
{ name: "Slack", src: "/logos/integrations/slack.svg" },
|
||||
{ name: "Google Drive", src: "/logos/integrations/google-drive.svg" },
|
||||
{ name: "GitHub", src: "/logos/integrations/github.svg" },
|
||||
]
|
||||
|
||||
const customerLogos = [
|
||||
{ name: "OpenAI", src: "/logos/openai.svg", width: 110, height: 31 },
|
||||
{ name: "Retool", src: "/logos/retool.svg", width: 113, height: 25 },
|
||||
{ name: "Stripe", src: "/logos/stripe.svg", width: 73, height: 31 },
|
||||
{ name: "Wise", src: "/logos/wise.svg", width: 96, height: 23 },
|
||||
{ name: "Tailwind CSS", src: "/logos/tailwindcss.svg", width: 262, height: 33, className: "w-full max-w-[158px] brightness-0" },
|
||||
{ name: "Medium", src: "/logos/medium.svg", width: 132, height: 21 },
|
||||
{ name: "Next.js", src: "/logos/nextjs.svg", width: 394, height: 80 },
|
||||
{ name: "Linear", src: "/logos/linear.svg", width: 122, height: 31 },
|
||||
]
|
||||
|
||||
const routingEvents = [
|
||||
{
|
||||
icon: <Route className="size-4" />,
|
||||
title: "Routed to Claude Sonnet",
|
||||
meta: "342ms · 8.2k tokens",
|
||||
accent: "bg-blue-600/15 text-blue-600 dark:text-blue-400",
|
||||
},
|
||||
{
|
||||
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 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 VertexMark({ className }: { className?: string }) {
|
||||
return (
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className={cn("brightness-0 dark:invert", className)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Brand({ compact = false }: { compact?: boolean }) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-2.5">
|
||||
<VertexMark className="size-8" />
|
||||
{!compact && (
|
||||
<span className="text-[17px] font-semibold tracking-[-0.03em]">Vertex</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function IntegrationMark({ src, name }: { src: string; name: string }) {
|
||||
return (
|
||||
<span
|
||||
className="inline-flex size-full items-center justify-center rounded-full border border-black/10 bg-white p-2 shadow-sm"
|
||||
title={name}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
alt={`${name} logo`}
|
||||
width={28}
|
||||
height={28}
|
||||
className="size-full object-contain"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function SectionHeading({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
className,
|
||||
}: {
|
||||
eyebrow: string
|
||||
title: string
|
||||
description: string
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("border-b px-6 py-16 text-center sm:px-10 sm:py-20", className)}>
|
||||
<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({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
}) {
|
||||
return (
|
||||
<div className="relative z-10 bg-background p-6 sm:p-7">
|
||||
<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 w-full border-b bg-background/86 backdrop-blur-xl supports-[backdrop-filter]:bg-background/74">
|
||||
<div className="mx-auto flex h-16 max-w-[1280px] 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-8 md:flex lg:gap-10">
|
||||
{[
|
||||
["Products", "#features"],
|
||||
["Models", "#models"],
|
||||
["Features", "#features"],
|
||||
["Pricing", "#pricing"],
|
||||
["Docs", "#developers"],
|
||||
].map(([label, href]) => (
|
||||
<a
|
||||
key={label}
|
||||
href={href}
|
||||
className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
{label}
|
||||
{label === "Products" && <ChevronDown className="size-3" />}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<a
|
||||
href="#pricing"
|
||||
className="hidden h-9 items-center gap-1.5 rounded-sm bg-blue-600 px-4 text-xs font-medium text-white shadow-[0_6px_24px_rgba(23,61,166,.28)] transition hover:bg-blue-500 sm:inline-flex"
|
||||
>
|
||||
Get started <ChevronRight className="size-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="top" className="mx-auto max-w-[1280px] border-x">
|
||||
<section className="hero-gradient relative border-b px-6 pb-14 pt-24 text-center sm:px-10 sm:pb-20 sm:pt-28 lg:pt-32">
|
||||
<span className="frame-cross left-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross right-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 left-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 right-0" aria-hidden="true" />
|
||||
<div className="relative z-10 mx-auto max-w-4xl">
|
||||
<a
|
||||
href="#features"
|
||||
className="mb-7 inline-flex items-center justify-center rounded-full border border-blue-600/15 bg-gradient-to-br from-white/80 via-blue-600/10 to-blue-600/20 px-5 py-2 text-xs font-medium text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,.6),0_4px_20px_rgba(23,61,166,.1)] backdrop-blur-xl transition-colors hover:border-blue-600/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:border-white/15 dark:from-white/10 dark:via-blue-600/15 dark:to-blue-600/30 dark:shadow-[inset_0_1px_0_rgba(255,255,255,.12),0_4px_20px_rgba(23,61,166,.15)] dark:hover:border-white/30"
|
||||
>
|
||||
The unified AI gateway
|
||||
</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">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-sm bg-blue-600 px-6 text-sm font-medium text-white shadow-[0_10px_32px_rgba(23,61,166,.32)] 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-sm border bg-background/70 px-6 text-sm font-medium transition hover:bg-muted sm:w-auto"
|
||||
>
|
||||
Explore models
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="models" className="border-b">
|
||||
<div className="relative flex min-h-[260px] items-center justify-center overflow-hidden border-b px-6 py-20 text-center sm:min-h-[340px] sm:py-28">
|
||||
<FlickeringGrid
|
||||
className="absolute inset-0 [mask-image:radial-gradient(520px_circle_at_center,white,transparent)]"
|
||||
squareSize={3}
|
||||
gridGap={8}
|
||||
color="#173DA6"
|
||||
maxOpacity={0.34}
|
||||
flickerChance={0.08}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_12%,var(--background)_78%)] opacity-70" />
|
||||
<h2 className="relative z-10 max-w-3xl text-balance text-4xl font-medium leading-[1.02] tracking-[-0.05em] sm:text-5xl md:text-6xl">
|
||||
Every leading model,
|
||||
<span className="block text-blue-600 dark:text-blue-400">one gateway.</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4">
|
||||
{customerLogos.map((logo) => (
|
||||
<div
|
||||
key={logo.name}
|
||||
className="group flex h-32 items-center justify-center border-r border-b px-6 last:border-r-0 even:border-r-0 sm:h-36 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"
|
||||
>
|
||||
<Image
|
||||
src={logo.src}
|
||||
alt={`${logo.name} logo`}
|
||||
width={logo.width}
|
||||
height={logo.height}
|
||||
className={cn("h-auto max-h-8 w-auto max-w-[132px] opacity-75 transition-all duration-200 group-hover:scale-[1.03] group-hover:opacity-100 dark:invert", logo.className)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features" className="border-b">
|
||||
<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-background 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-blue-600 dark:text-blue-400">
|
||||
→ 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
|
||||
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-background">
|
||||
<VertexMark className="absolute left-1/2 top-1/2 z-10 size-12 -translate-x-1/2 -translate-y-1/2" />
|
||||
<OrbitingCircles className="z-10" radius={84} iconSize={44} duration={16}>
|
||||
{integrations.slice(0, 4).map((integration) => (
|
||||
<IntegrationMark key={integration.name} {...integration} />
|
||||
))}
|
||||
</OrbitingCircles>
|
||||
<OrbitingCircles className="z-10" radius={150} iconSize={44} duration={24} reverse>
|
||||
{integrations.slice(4).map((integration) => (
|
||||
<IntegrationMark key={integration.name} {...integration} />
|
||||
))}
|
||||
</OrbitingCircles>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-32 bg-gradient-to-t from-background via-background/80 to-transparent"
|
||||
/>
|
||||
</div>
|
||||
<FeatureCopy
|
||||
title="Seamless integrations"
|
||||
description="Connect your favorite tools and bring AI into the workflows your team already uses."
|
||||
/>
|
||||
</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-[360px] shrink-0 overflow-hidden bg-background">
|
||||
<AnimatedList delay={1150} className="absolute inset-x-4 top-4 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
|
||||
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="relative h-[360px] shrink-0 overflow-hidden bg-background">
|
||||
<UsageChart />
|
||||
</div>
|
||||
<FeatureCopy
|
||||
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-[360px] shrink-0 overflow-hidden bg-background">
|
||||
<Globe className="top-6 max-w-[360px]" config={globeConfig} />
|
||||
<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
|
||||
title="Global inference gateway"
|
||||
description="Route requests through the nearest healthy region for consistently fast model access."
|
||||
/>
|
||||
</article>
|
||||
</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">
|
||||
{[
|
||||
{
|
||||
icon: Code2,
|
||||
title: "Change one base URL",
|
||||
text: "Keep your existing OpenAI-compatible SDK, payloads, and streaming logic.",
|
||||
},
|
||||
{
|
||||
icon: Workflow,
|
||||
title: "Set a routing policy",
|
||||
text: "Choose a model directly or let Vertex optimize for cost, quality, or latency.",
|
||||
},
|
||||
{
|
||||
icon: Gauge,
|
||||
title: "Observe every request",
|
||||
text: "Trace performance and spend across models, applications, and environments.",
|
||||
},
|
||||
].map((step, index) => (
|
||||
<article key={step.title} className={cn("relative p-8 sm:p-10", index < 2 && "border-b md:border-b-0 md:border-r")}>
|
||||
<step.icon className="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."
|
||||
className="border-b-0"
|
||||
/>
|
||||
<div className="flex justify-center border-b py-6">
|
||||
<div className="inline-flex rounded-sm border bg-muted/40 p-1" aria-label="Billing cadence">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={!annual}
|
||||
onClick={() => setAnnual(false)}
|
||||
className={cn("rounded-sm 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-sm 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", plan.popular && "pricing-card-popular")}>
|
||||
<div className="relative z-10 flex flex-1 flex-col p-7 sm:p-9">
|
||||
<div className="flex min-h-7 flex-wrap items-center justify-between gap-2">
|
||||
<p className="text-sm font-medium">{plan.name}</p>
|
||||
{plan.popular && (
|
||||
<span className="rounded-full border border-blue-600/40 px-3 py-1 text-xs font-medium text-blue-600 dark:border-[#5479db]/40 dark:text-blue-400">
|
||||
Most popular
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<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-sm 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>
|
||||
<ul className="mt-7 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>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</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="relative border-b">
|
||||
<span className="frame-cross left-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross right-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 left-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 right-0" 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="#173DA6"
|
||||
maxOpacity={0.42}
|
||||
flickerChance={0.08}
|
||||
/>
|
||||
<div className="relative z-10 mx-auto max-w-3xl">
|
||||
<VertexMark className="mx-auto mb-7 size-12" />
|
||||
<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>
|
||||
</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 text-[11px] text-muted-foreground">
|
||||
<span>© 2026 Vertex, Inc. All rights reserved.</span>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user