Atlas
AI agent platform with an auto-playing chat demo, use-case explorer and waitlist.
app/page.tsx
"use client";
import { AnimatePresence, motion } from "motion/react";
import {
ArrowRight,
ArrowUp,
Bot,
Brain,
Code2,
FileSearch,
Globe,
Headphones,
LineChart,
Mail,
Shield,
Sparkles,
Workflow,
} from "lucide-react";
import { useEffect, useState } from "react";
const ease = [0.22, 1, 0.36, 1] as const;
const reveal = {
initial: { opacity: 0, y: 28 },
whileInView: { opacity: 1, y: 0 },
viewport: { once: true, amount: 0.3 },
transition: { duration: 0.8, ease },
};
const conversation = [
{ from: "user", text: "Find every enterprise lead from last week that hasn't had a follow-up." },
{ from: "agent", text: "Found 14 leads across HubSpot and Gmail. 9 have no reply in 5+ days. I drafted personalised follow-ups for each — want me to send them?" },
{ from: "user", text: "Yes, and book intro calls for anyone who replies." },
{ from: "agent", text: "Done. 9 emails sent. I'll watch for replies and add calls to your calendar automatically." },
];
function ChatDemo() {
const [count, setCount] = useState(1);
useEffect(() => {
if (count >= conversation.length) {
const reset = setTimeout(() => setCount(1), 4000);
return () => clearTimeout(reset);
}
const t = setTimeout(() => setCount((c) => c + 1), 1900);
return () => clearTimeout(t);
}, [count]);
return (
<div className="rounded-3xl border border-violet-100 bg-white/90 p-2 shadow-[0_40px_120px_-30px_rgba(109,40,217,0.35)] backdrop-blur">
<div className="rounded-[20px] border border-zinc-100 bg-white">
<div className="flex items-center gap-2 border-b border-zinc-100 px-5 py-3.5">
<span className="grid size-7 place-items-center rounded-full bg-gradient-to-br from-violet-500 to-indigo-600 text-white">
<Bot className="size-4" />
</span>
<span className="text-sm font-medium text-zinc-900">Atlas Agent</span>
<span className="ml-auto flex items-center gap-1.5 text-xs text-emerald-600">
<span className="size-1.5 animate-pulse rounded-full bg-emerald-500" /> Working
</span>
</div>
<div className="flex h-80 flex-col justify-end gap-3 overflow-hidden p-5">
<AnimatePresence initial={false}>
{conversation.slice(0, count).map((m, i) => (
<motion.div
key={i}
layout
initial={{ opacity: 0, y: 16, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.45, ease }}
className={`max-w-[85%] rounded-2xl px-4 py-3 text-left text-sm leading-relaxed ${
m.from === "user" ? "ml-auto rounded-br-md bg-zinc-900 text-white" : "rounded-bl-md bg-violet-50 text-zinc-800"
}`}
>
{m.text}
</motion.div>
))}
</AnimatePresence>
</div>
<div className="flex items-center gap-2 border-t border-zinc-100 p-3">
<div className="flex-1 rounded-full bg-zinc-50 px-4 py-2.5 text-left text-sm text-zinc-400">Ask Atlas to do anything…</div>
<span className="grid size-9 place-items-center rounded-full bg-violet-600 text-white">
<ArrowUp className="size-4" />
</span>
</div>
</div>
</div>
);
}
function Hero() {
return (
<section className="relative isolate overflow-hidden px-6 pb-24 pt-16">
<motion.div aria-hidden className="absolute -left-40 -top-40 -z-10 size-[36rem] rounded-full bg-violet-200/60 blur-3xl" animate={{ x: [0, 60, 0], y: [0, 40, 0] }} transition={{ duration: 16, repeat: Infinity, ease: "easeInOut" }} />
<motion.div aria-hidden className="absolute -right-40 top-20 -z-10 size-[30rem] rounded-full bg-indigo-200/60 blur-3xl" animate={{ x: [0, -50, 0], y: [0, 60, 0] }} transition={{ duration: 18, repeat: Infinity, ease: "easeInOut" }} />
<div className="mx-auto grid max-w-6xl items-center gap-14 lg:grid-cols-2">
<div>
<motion.span initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="inline-flex items-center gap-2 rounded-full bg-violet-100 px-3 py-1 text-xs font-medium text-violet-700">
<Sparkles className="size-3.5" /> Introducing Atlas Agents
</motion.span>
<motion.h1 initial={{ opacity: 0, y: 24, filter: "blur(10px)" }} animate={{ opacity: 1, y: 0, filter: "blur(0)" }} transition={{ duration: 0.9, ease }} className="mt-6 text-balance text-5xl font-semibold leading-[1.05] tracking-tight text-zinc-900 sm:text-6xl">
AI teammates that{" "}
<span className="bg-gradient-to-r from-violet-600 to-indigo-600 bg-clip-text text-transparent">actually get work done</span>
</motion.h1>
<motion.p initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, delay: 0.2, ease }} className="mt-6 max-w-lg text-lg text-zinc-500">
Atlas agents work across your inbox, CRM, docs and code — handling the busywork so your team can focus on what matters.
</motion.p>
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, delay: 0.35, ease }} className="mt-9 flex flex-wrap gap-3">
<a href="#" className="group inline-flex items-center gap-2 rounded-full bg-zinc-900 px-6 py-3 text-sm font-medium text-white transition hover:-translate-y-0.5">
Get early access <ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
</a>
<a href="#use-cases" className="rounded-full border border-zinc-200 bg-white px-6 py-3 text-sm font-medium text-zinc-900 hover:bg-zinc-50">
See use cases
</a>
</motion.div>
<div className="mt-10 flex items-center gap-6 text-sm text-zinc-500">
<span className="flex items-center gap-2"><Shield className="size-4 text-violet-600" /> SOC 2 Type II</span>
<span className="flex items-center gap-2"><Globe className="size-4 text-violet-600" /> 40+ languages</span>
</div>
</div>
<motion.div initial={{ opacity: 0, y: 40, scale: 0.96 }} animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ duration: 1, delay: 0.3, ease }}>
<ChatDemo />
</motion.div>
</div>
</section>
);
}
const useCases = [
{ id: "sales", icon: LineChart, label: "Sales", title: "Never miss a follow-up", text: "Atlas qualifies inbound leads, enriches records and drafts personalised outreach in your voice.", metric: "3.2×", metricLabel: "more meetings booked" },
{ id: "support", icon: Headphones, label: "Support", title: "Resolve tickets instantly", text: "Answers common questions using your docs, escalates edge cases with full context to a human.", metric: "68%", metricLabel: "of tickets auto-resolved" },
{ id: "eng", icon: Code2, label: "Engineering", title: "Ship with fewer interruptions", text: "Triage bugs, write first-draft fixes and keep tickets up to date across GitHub and Linear.", metric: "11h", metricLabel: "saved per engineer / week" },
{ id: "ops", icon: Workflow, label: "Operations", title: "Automate the in-between", text: "Reconcile invoices, update spreadsheets and keep systems in sync without brittle scripts.", metric: "94%", metricLabel: "fewer manual steps" },
];
function UseCases() {
const [active, setActive] = useState(useCases[0].id);
const current = useCases.find((u) => u.id === active)!;
return (
<section id="use-cases" className="px-6 py-24">
<div className="mx-auto max-w-6xl">
<motion.div {...reveal} className="text-center">
<p className="text-sm font-semibold text-violet-600">Use cases</p>
<h2 className="mt-3 text-4xl font-semibold tracking-tight text-zinc-900 sm:text-5xl">One platform, every team</h2>
</motion.div>
<div className="mt-12 grid gap-6 lg:grid-cols-[280px_1fr]">
<div className="flex gap-2 overflow-x-auto lg:flex-col">
{useCases.map((u) => (
<button key={u.id} onClick={() => setActive(u.id)} className={`relative flex shrink-0 items-center gap-3 rounded-2xl px-5 py-4 text-left text-sm font-medium transition-colors ${active === u.id ? "text-violet-700" : "text-zinc-600 hover:bg-zinc-50"}`}>
{active === u.id && <motion.span layoutId="atlas-case" className="absolute inset-0 rounded-2xl border border-violet-200 bg-violet-50" transition={{ type: "spring", stiffness: 350, damping: 30 }} />}
<u.icon className="relative size-5" />
<span className="relative">{u.label}</span>
</button>
))}
</div>
<AnimatePresence mode="wait">
<motion.div key={current.id} initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} transition={{ duration: 0.35 }} className="grid gap-8 rounded-3xl border border-zinc-200 bg-gradient-to-br from-white to-violet-50/50 p-10 md:grid-cols-[1fr_auto] md:items-end">
<div>
<current.icon className="size-10 text-violet-600" strokeWidth={1.5} />
<h3 className="mt-6 text-3xl font-semibold tracking-tight text-zinc-900">{current.title}</h3>
<p className="mt-3 max-w-md text-zinc-500">{current.text}</p>
</div>
<div className="rounded-2xl bg-white p-6 shadow-sm ring-1 ring-zinc-200">
<p className="text-5xl font-semibold tracking-tight text-zinc-900">{current.metric}</p>
<p className="mt-1 text-sm text-zinc-500">{current.metricLabel}</p>
</div>
</motion.div>
</AnimatePresence>
</div>
</div>
</section>
);
}
const capabilities = [
{ icon: Brain, title: "Reasons step by step", text: "Plans multi-step work, checks its results and asks when unsure." },
{ icon: FileSearch, title: "Knows your company", text: "Grounded in your docs, tickets and wiki with source citations." },
{ icon: Mail, title: "Works where you work", text: "Lives in Slack, Gmail, Notion, HubSpot, GitHub and 100+ apps." },
{ icon: Shield, title: "Safe by design", text: "Granular permissions, approval steps and a full audit trail." },
];
function Capabilities() {
return (
<section className="bg-zinc-950 px-6 py-24 text-white">
<div className="mx-auto max-w-6xl">
<motion.h2 {...reveal} className="max-w-2xl text-4xl font-semibold tracking-tight sm:text-5xl">
Built for real work, <span className="text-zinc-500">not just chat</span>
</motion.h2>
<div className="mt-14 grid gap-px overflow-hidden rounded-3xl bg-white/10 sm:grid-cols-2">
{capabilities.map((c, i) => (
<motion.div key={c.title} {...reveal} transition={{ ...reveal.transition, delay: i * 0.08 }} className="group bg-zinc-950 p-10 transition-colors hover:bg-zinc-900">
<c.icon className="size-7 text-violet-400 transition-transform group-hover:scale-110" />
<h3 className="mt-6 text-xl font-semibold">{c.title}</h3>
<p className="mt-2 text-zinc-400">{c.text}</p>
</motion.div>
))}
</div>
</div>
</section>
);
}
function Integrations() {
const apps = ["Slack", "Gmail", "Notion", "HubSpot", "GitHub", "Linear", "Salesforce", "Zendesk", "Drive", "Jira", "Stripe", "Figma"];
return (
<section className="px-6 py-24">
<div className="mx-auto max-w-4xl text-center">
<motion.h2 {...reveal} className="text-4xl font-semibold tracking-tight text-zinc-900">Connects to the tools you already use</motion.h2>
<div className="mt-12 flex flex-wrap justify-center gap-3">
{apps.map((a, i) => (
<motion.span key={a} initial={{ opacity: 0, scale: 0.8 }} whileInView={{ opacity: 1, scale: 1 }} viewport={{ once: true }} transition={{ delay: i * 0.04, type: "spring", stiffness: 260, damping: 18 }} whileHover={{ y: -4 }} className="rounded-full border border-zinc-200 bg-white px-5 py-2.5 text-sm font-medium text-zinc-700 shadow-sm">
{a}
</motion.span>
))}
</div>
</div>
</section>
);
}
function Cta() {
return (
<section className="px-6 pb-24">
<motion.div {...reveal} className="relative isolate mx-auto max-w-6xl overflow-hidden rounded-[2rem] bg-gradient-to-br from-violet-600 to-indigo-700 px-8 py-20 text-center text-white">
<div aria-hidden className="absolute inset-0 -z-10 opacity-30 [background-image:radial-gradient(rgba(255,255,255,0.4)_1px,transparent_1px)] [background-size:20px_20px]" />
<h2 className="mx-auto max-w-2xl text-4xl font-semibold tracking-tight sm:text-5xl">Give your team its first AI hire</h2>
<p className="mx-auto mt-5 max-w-lg text-violet-100">Free for 14 days. Set up your first agent in under ten minutes.</p>
<form onSubmit={(e) => e.preventDefault()} className="mx-auto mt-10 flex max-w-md gap-2 rounded-full bg-white p-1.5">
<input type="email" placeholder="Work email" className="min-w-0 flex-1 bg-transparent px-4 text-sm text-zinc-900 outline-none" />
<button className="rounded-full bg-zinc-900 px-5 py-2.5 text-sm font-medium text-white">Join waitlist</button>
</form>
</motion.div>
</section>
);
}
export default function AtlasTemplate() {
return (
<div className="min-h-screen bg-white text-zinc-900 antialiased">
<header className="sticky top-0 z-50 bg-white/70 backdrop-blur-lg">
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
<span className="flex items-center gap-2 font-semibold">
<span className="grid size-8 place-items-center rounded-full bg-gradient-to-br from-violet-500 to-indigo-600 text-white"><Bot className="size-4" /></span>
Atlas
</span>
<nav className="hidden gap-8 text-sm text-zinc-600 md:flex">
<a href="#use-cases" className="hover:text-zinc-900">Use cases</a>
<a href="#" className="hover:text-zinc-900">Security</a>
<a href="#" className="hover:text-zinc-900">Pricing</a>
<a href="#" className="hover:text-zinc-900">Company</a>
</nav>
<a href="#" className="rounded-full bg-zinc-900 px-4 py-2 text-sm font-medium text-white">Get access</a>
</div>
</header>
<main>
<Hero />
<UseCases />
<Capabilities />
<Integrations />
<Cta />
</main>
<footer className="border-t border-zinc-100 px-6 py-10">
<div className="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-4 text-sm text-zinc-500">
<span>© {new Date().getFullYear()} Atlas Labs</span>
<div className="flex gap-6">
<a href="#" className="hover:text-zinc-900">Privacy</a>
<a href="#" className="hover:text-zinc-900">Terms</a>
<a href="#" className="hover:text-zinc-900">Status</a>
</div>
</div>
</footer>
</div>
);
}
What's included
- Navbar
- Hero + chat demo
- Use cases
- Capabilities
- Integrations
- Waitlist CTA
- Footer
Use it in 3 steps
1. Install dependencies in your Next.js + Tailwind project:
$ npm i motion lucide-react- 2. Copy the code into
app/page.tsx. - 3. Replace the copy, colors and links with your own — then deploy.