Template
Improve mobile navigation, responsive layout, and theme styling
This commit is contained in:
+1
-1
@@ -169,7 +169,7 @@
|
||||
background-size: 64px 100%;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
@apply scroll-pt-20 font-sans;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import { TestimonialsSection } from "@/components/sections/testimonials-section"
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-foreground selection:bg-blue-600 selection:text-white">
|
||||
<div className="min-h-screen overflow-x-clip bg-background text-foreground selection:bg-blue-600 selection:text-white">
|
||||
<SiteHeader />
|
||||
<main id="top" className="mx-auto max-w-[1280px] border-x">
|
||||
<HeroSection />
|
||||
@@ -22,8 +22,8 @@ export default function Home() {
|
||||
<PricingSection />
|
||||
<TestimonialsSection />
|
||||
<CtaSection />
|
||||
<SiteFooter />
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { RiArrowRightLine, RiCloseLine, RiMenu3Line } from "@remixicon/react"
|
||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||
|
||||
import { Brand } from "@/components/shared/brand"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogDescription,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
type NavigationItem = {
|
||||
label: string
|
||||
href: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export function MobileNavigation({ items }: { items: NavigationItem[] }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const navigating = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const desktop = window.matchMedia("(min-width: 768px)")
|
||||
const closeOnDesktop = () => {
|
||||
if (desktop.matches) setOpen(false)
|
||||
}
|
||||
|
||||
desktop.addEventListener("change", closeOnDesktop)
|
||||
return () => desktop.removeEventListener("change", closeOnDesktop)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Open navigation menu"
|
||||
className="inline-flex size-11 items-center justify-center rounded-sm border bg-background/80 text-foreground transition-colors hover:border-blue-500/40 hover:bg-blue-600/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:hidden"
|
||||
>
|
||||
<RiMenu3Line className="size-5" aria-hidden="true" />
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogPortal>
|
||||
<DialogOverlay className="z-[60] bg-black/45 duration-200 supports-backdrop-filter:backdrop-blur-md md:hidden" />
|
||||
<DialogPrimitive.Content
|
||||
className="fixed inset-x-3 top-3 z-[61] mx-auto flex max-h-[calc(100dvh-1.5rem)] max-w-lg flex-col overflow-hidden rounded-2xl border bg-background text-foreground shadow-[0_24px_100px_rgba(0,0,0,.35)] outline-none duration-200 data-open:animate-in data-open:fade-in-0 data-open:slide-in-from-top-4 data-closed:animate-out data-closed:fade-out-0 data-closed:slide-out-to-top-4 md:hidden"
|
||||
onCloseAutoFocus={(event) => {
|
||||
// Let the anchor move focus to its section without the trigger
|
||||
// interrupting the page's smooth scroll when the dialog closes.
|
||||
if (navigating.current) event.preventDefault()
|
||||
navigating.current = false
|
||||
}}
|
||||
>
|
||||
<div className="flex shrink-0 items-center justify-between border-b bg-linear-to-r from-blue-600/10 via-blue-600/5 to-transparent px-5 py-3">
|
||||
<Brand />
|
||||
<DialogClose asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close navigation menu"
|
||||
className="inline-flex size-11 items-center justify-center rounded-full border bg-background/70 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<RiCloseLine className="size-5" aria-hidden="true" />
|
||||
</button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 overflow-y-auto overscroll-contain p-3">
|
||||
<DialogTitle className="px-3 pb-2 pt-3 font-mono text-[10px] font-medium tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Explore Vertex
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Explore our products, models, features, pricing, and developer documentation.
|
||||
</DialogDescription>
|
||||
<nav aria-label="Mobile navigation">
|
||||
<ul className="space-y-1">
|
||||
{items.map(({ label, href, description }) => (
|
||||
<li key={label}>
|
||||
<DialogClose asChild>
|
||||
<a
|
||||
href={href}
|
||||
onClick={() => { navigating.current = true }}
|
||||
className="group flex items-center justify-between gap-4 rounded-xl px-3 py-3 transition-colors hover:bg-blue-600/8 focus-visible:bg-blue-600/8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
||||
>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-xl font-medium tracking-tight">{label}</span>
|
||||
<span className="mt-0.5 block text-xs leading-5 text-muted-foreground">{description}</span>
|
||||
</span>
|
||||
<span className="flex size-8 shrink-0 items-center justify-center rounded-full border text-muted-foreground transition-colors group-hover:border-blue-500/30 group-hover:bg-blue-600/10 group-hover:text-blue-600 dark:group-hover:text-blue-400">
|
||||
<RiArrowRightLine className="size-4" aria-hidden="true" />
|
||||
</span>
|
||||
</a>
|
||||
</DialogClose>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 border-t bg-muted/35 p-5">
|
||||
<p className="mb-3 text-xs text-muted-foreground">One API. Every AI model.</p>
|
||||
<DialogClose asChild>
|
||||
<a
|
||||
href="#pricing"
|
||||
onClick={() => { navigating.current = true }}
|
||||
className="flex h-12 items-center justify-between rounded-lg bg-blue-600 px-4 text-sm font-medium text-white shadow-[0_6px_24px_rgba(23,61,166,.2)] transition-colors hover:bg-blue-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
||||
>
|
||||
Get started
|
||||
<RiArrowRightLine className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="px-6 py-10 sm:px-10">
|
||||
<footer className="mx-auto max-w-[1280px] border-x 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 />
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from "@remixicon/react"
|
||||
|
||||
import { MobileNavigation } from "@/components/layout/mobile-navigation"
|
||||
import { ThemeToggle } from "@/components/layout/theme-toggle"
|
||||
import { Brand } from "@/components/shared/brand"
|
||||
import { FrameCrosses } from "@/components/shared/frame-crosses"
|
||||
|
||||
const navigationItems = [
|
||||
{ label: "Products", href: "#features", description: "One gateway for your AI stack" },
|
||||
{ label: "Models", href: "#models", description: "Explore the models you can build with" },
|
||||
{ label: "Features", href: "#features", description: "Routing, insights, and reliability" },
|
||||
{ label: "Pricing", href: "#pricing", description: "Start free. Scale at your pace." },
|
||||
{ label: "Docs", href: "#developers", description: "Your first request starts here" },
|
||||
]
|
||||
|
||||
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">
|
||||
<div className="relative mx-auto flex h-16 max-w-[1280px] items-center justify-between border-x px-5 sm:px-8">
|
||||
<FrameCrosses edges="bottom" />
|
||||
<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]) => (
|
||||
{navigationItems.map(({ label, href }) => (
|
||||
<a
|
||||
key={label}
|
||||
href={href}
|
||||
@@ -36,6 +41,7 @@ export function SiteHeader() {
|
||||
>
|
||||
Get started <RiArrowRightSLine className="size-3.5" />
|
||||
</a>
|
||||
<MobileNavigation items={navigationItems} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -15,8 +15,8 @@ export function FeaturesSection() {
|
||||
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="grid grid-cols-1 lg:grid-cols-12">
|
||||
<article className="flex min-h-[520px] min-w-0 flex-col border-b lg:col-span-7 lg:border-r">
|
||||
<ApiTerminal />
|
||||
<FeatureCopy
|
||||
title="One compatible endpoint"
|
||||
@@ -24,7 +24,7 @@ export function FeaturesSection() {
|
||||
/>
|
||||
</article>
|
||||
|
||||
<article className="flex min-h-[520px] flex-col border-b lg:col-span-5">
|
||||
<article className="flex min-h-[520px] min-w-0 flex-col border-b lg:col-span-5">
|
||||
<IntegrationOrbit />
|
||||
<FeatureCopy
|
||||
title="Seamless integrations"
|
||||
@@ -32,7 +32,7 @@ export function FeaturesSection() {
|
||||
/>
|
||||
</article>
|
||||
|
||||
<article className="flex min-h-[430px] flex-col border-b lg:col-span-4 lg:border-b-0 lg:border-r">
|
||||
<article className="flex min-h-[430px] min-w-0 flex-col border-b lg:col-span-4 lg:border-b-0 lg:border-r">
|
||||
<RoutingActivity />
|
||||
<FeatureCopy
|
||||
title="Automatic failover"
|
||||
@@ -40,8 +40,8 @@ export function FeaturesSection() {
|
||||
/>
|
||||
</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">
|
||||
<article className="flex min-h-[430px] min-w-0 flex-col border-b lg:col-span-4 lg:border-b-0 lg:border-r">
|
||||
<div className="relative h-[360px] w-full min-w-0 shrink-0 overflow-hidden bg-background">
|
||||
<UsageChart />
|
||||
</div>
|
||||
<FeatureCopy
|
||||
@@ -50,7 +50,7 @@ export function FeaturesSection() {
|
||||
/>
|
||||
</article>
|
||||
|
||||
<article className="flex min-h-[430px] flex-col lg:col-span-4">
|
||||
<article className="flex min-h-[430px] min-w-0 flex-col lg:col-span-4">
|
||||
<GlobalGlobe />
|
||||
<FeatureCopy
|
||||
title="Global inference gateway"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import { type COBEOptions } from "cobe"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Globe } from "@/components/ui/globe"
|
||||
|
||||
const globeConfig = {
|
||||
const darkGlobeConfig: COBEOptions = {
|
||||
width: 800,
|
||||
height: 800,
|
||||
onRender: () => {},
|
||||
@@ -28,13 +31,25 @@ const globeConfig = {
|
||||
],
|
||||
}
|
||||
|
||||
const lightGlobeConfig: COBEOptions = {
|
||||
...darkGlobeConfig,
|
||||
dark: 0,
|
||||
diffuse: 0.4,
|
||||
mapBrightness: 1.2,
|
||||
baseColor: [0.9, 0.94, 1],
|
||||
markerColor: [0.09, 0.24, 0.65],
|
||||
glowColor: [0.85, 0.9, 1],
|
||||
}
|
||||
|
||||
export function GlobalGlobe() {
|
||||
const { resolvedTheme } = useTheme()
|
||||
|
||||
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>
|
||||
<Globe
|
||||
className="top-6 max-w-[360px]"
|
||||
config={resolvedTheme === "light" ? lightGlobeConfig : darkGlobeConfig}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function UsageChart() {
|
||||
const fillId = useId()
|
||||
|
||||
return (
|
||||
<ChartContainer config={chartConfig} className="h-full min-h-[270px] w-full aspect-auto">
|
||||
<ChartContainer config={chartConfig} className="h-full min-h-[270px] w-full min-w-0 aspect-auto">
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
aria-label="Example model usage: requests increase from 2,350 to 5,680 over the day. Use the arrow keys to explore."
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 />
|
||||
<FrameCrosses edges="bottom" />
|
||||
<div className="relative z-10 mx-auto max-w-4xl">
|
||||
<a
|
||||
href="#features"
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
export function FrameCrosses() {
|
||||
export function FrameCrosses({
|
||||
edges = "all",
|
||||
}: {
|
||||
edges?: "all" | "top" | "bottom"
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{edges !== "bottom" && (
|
||||
<>
|
||||
<span className="frame-cross left-0 top-0" aria-hidden="true" />
|
||||
<span className="frame-cross right-0 top-0" aria-hidden="true" />
|
||||
</>
|
||||
)}
|
||||
{edges !== "top" && (
|
||||
<>
|
||||
<span className="frame-cross bottom-0 left-0" aria-hidden="true" />
|
||||
<span className="frame-cross bottom-0 right-0" aria-hidden="true" />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user