feat(landing): enhance landing page with new enterprise design and features

This commit is contained in:
Abhimanyu Saharan
2026-02-07 14:01:04 +05:30
parent a7175d9a6f
commit 2c13c5b5ce
7 changed files with 1341 additions and 130 deletions

View File

@@ -1,105 +1,269 @@
"use client";
import { SignInButton, SignedIn, SignedOut } from "@/auth/clerk";
import Link from "next/link";
import { HeroCopy } from "@/components/molecules/HeroCopy";
import { Button } from "@/components/ui/button";
import { SignInButton, SignedIn, SignedOut, isClerkEnabled } from "@/auth/clerk";
const ArrowIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path
d="M6 12L10 8L6 4"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function LandingHero() {
return (
<section className="grid w-full items-center gap-12 lg:grid-cols-[1.1fr_0.9fr]">
<div className="space-y-8 animate-fade-in-up">
<HeroCopy />
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<SignedOut>
<SignInButton
mode="modal"
forceRedirectUrl="/onboarding"
signUpForceRedirectUrl="/onboarding"
>
<Button size="lg" className="w-full sm:w-auto">
Sign in to open mission control
</Button>
</SignInButton>
</SignedOut>
<SignedIn>
<div className="text-sm text-muted">
You&apos;re signed in. Open your boards when you&apos;re ready.
</div>
</SignedIn>
</div>
<div className="flex flex-wrap gap-3 text-xs font-semibold uppercase tracking-[0.28em] text-quiet">
<span className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1">
Enterprise ready
</span>
<span className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1">
Agent-first ops
</span>
<span className="rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-1">
24/7 visibility
</span>
</div>
</div>
const clerkEnabled = isClerkEnabled();
<div className="relative animate-fade-in-up">
<div className="surface-panel rounded-3xl p-6">
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.3em] text-quiet">
<span>Command surface</span>
<span className="rounded-full border border-[color:var(--border)] px-2 py-1 text-[10px]">
Live
</span>
return (
<>
<section className="hero">
<div className="hero-content">
<div className="hero-label">OpenClaw Mission Control</div>
<h1>
Command <span className="hero-highlight">autonomous work.</span>
<br />
Keep human oversight.
</h1>
<p>
Track tasks, approvals, and agent health in one unified command
center. Get real-time signals when work changes, without losing the
thread of execution.
</p>
<div className="hero-actions">
<SignedOut>
{clerkEnabled ? (
<>
<SignInButton
mode="modal"
forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards"
>
<button type="button" className="btn-large primary">
Open Boards <ArrowIcon />
</button>
</SignInButton>
<SignInButton
mode="modal"
forceRedirectUrl="/boards/new"
signUpForceRedirectUrl="/boards/new"
>
<button type="button" className="btn-large secondary">
Create Board
</button>
</SignInButton>
</>
) : (
<>
<Link href="/boards" className="btn-large primary">
Open Boards <ArrowIcon />
</Link>
<Link href="/boards/new" className="btn-large secondary">
Create Board
</Link>
</>
)}
</SignedOut>
<SignedIn>
<Link href="/boards" className="btn-large primary">
Open Boards <ArrowIcon />
</Link>
<Link href="/boards/new" className="btn-large secondary">
Create Board
</Link>
</SignedIn>
</div>
<div className="mt-6 space-y-4">
<div>
<p className="text-lg font-semibold text-strong">
Tasks claimed, tracked, delivered.
</p>
<p className="text-sm text-muted">
See every queue, agent, and handoff without chasing updates.
</p>
<div className="hero-features">
{[
"Agent-First Operations",
"Approval Queues",
"Live Signals",
].map((label) => (
<div key={label} className="hero-feature">
<div className="feature-icon"></div>
<span>{label}</span>
</div>
))}
</div>
</div>
<div className="command-surface">
<div className="surface-header">
<div className="surface-title">Command Surface</div>
<div className="live-indicator">
<div className="live-dot" />
LIVE
</div>
<div className="grid grid-cols-3 gap-3">
</div>
<div className="surface-subtitle">
<h3>Ship work without losing the thread.</h3>
<p>Tasks, approvals, and agent status stay synced across the board.</p>
</div>
<div className="metrics-row">
{[
{ label: "Boards", value: "12" },
{ label: "Agents", value: "08" },
{ label: "Tasks", value: "46" },
].map((item) => (
<div key={item.label} className="metric">
<div className="metric-value">{item.value}</div>
<div className="metric-label">{item.label}</div>
</div>
))}
</div>
<div className="surface-content">
<div className="content-section">
<h4>Board In Progress</h4>
{[
{ label: "Active boards", value: "12" },
{ label: "Agents live", value: "08" },
{ label: "Tasks in flow", value: "46" },
].map((item) => (
<div
key={item.label}
className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-4 text-center"
>
<div className="text-xl font-semibold text-strong">
{item.value}
</div>
<div className="text-[11px] uppercase tracking-[0.18em] text-quiet">
{item.label}
"Cut release candidate",
"Triage approvals backlog",
"Stabilize agent handoffs",
].map((title) => (
<div key={title} className="status-item">
<div className="status-icon progress"></div>
<div className="status-item-content">
<div className="status-item-title">{title}</div>
</div>
</div>
))}
</div>
<div className="rounded-2xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4">
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-[0.2em] text-quiet">
<span>Signals</span>
<span>Updated 2m ago</span>
</div>
<div className="mt-3 space-y-2 text-sm text-muted">
<div className="flex items-center justify-between">
<span>Agent Delta moved task to review</span>
<span className="text-quiet">Just now</span>
<div className="content-section">
<h4>Approvals 3 Pending</h4>
{[
{ title: "Deploy window confirmed", status: "ready" as const },
{ title: "Copy reviewed", status: "waiting" as const },
{ title: "Security sign-off", status: "waiting" as const },
].map((item) => (
<div key={item.title} className="approval-item">
<div className="approval-title">{item.title}</div>
<div className={`approval-badge ${item.status}`}>
{item.status}
</div>
</div>
<div className="flex items-center justify-between">
<span>Board Growth Ops hit WIP limit</span>
<span className="text-quiet">5m</span>
))}
</div>
</div>
<div
style={{
padding: "2rem",
borderTop: "1px solid var(--neutral-200)",
}}
>
<div className="content-section">
<h4>Signals Updated Moments Ago</h4>
{[
{ text: "Agent Delta moved task to review", time: "Now" },
{ text: "Growth Ops hit WIP limit", time: "5m" },
{ text: "Release pipeline stabilized", time: "12m" },
].map((signal) => (
<div key={signal.text} className="signal-item">
<div className="signal-text">{signal.text}</div>
<div className="signal-time">{signal.time}</div>
</div>
<div className="flex items-center justify-between">
<span>Release tasks stabilized</span>
<span className="text-quiet">12m</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
</section>
<section className="features-section" id="capabilities">
<div className="features-grid">
{[
{
title: "Boards as ops maps",
description:
"Keep tasks, priorities, dependencies, and ownership visible at a glance.",
},
{
title: "Approvals that move",
description:
"Queue, comment, and approve without losing context or slowing execution.",
},
{
title: "Realtime signals",
description:
"See work change as it happens: tasks, agent status, and approvals update live.",
},
{
title: "Audit trail built in",
description:
"Every decision leaves a trail, so the board stays explainable and reviewable.",
},
].map((feature, idx) => (
<div key={feature.title} className="feature-card">
<div className="feature-number">
{String(idx + 1).padStart(2, "0")}
</div>
<h3>{feature.title}</h3>
<p>{feature.description}</p>
</div>
))}
</div>
</section>
<section className="cta-section">
<div className="cta-content">
<h2>Start with one board. Grow into a control room.</h2>
<p>
Onboard a board, name a lead agent, and keep approvals and signals
visible from day one.
</p>
<div className="cta-actions">
<SignedOut>
{clerkEnabled ? (
<>
<SignInButton
mode="modal"
forceRedirectUrl="/boards/new"
signUpForceRedirectUrl="/boards/new"
>
<button type="button" className="btn-large white">
Create Board
</button>
</SignInButton>
<SignInButton
mode="modal"
forceRedirectUrl="/boards"
signUpForceRedirectUrl="/boards"
>
<button type="button" className="btn-large outline">
View Boards
</button>
</SignInButton>
</>
) : (
<>
<Link href="/boards/new" className="btn-large white">
Create Board
</Link>
<Link href="/boards" className="btn-large outline">
View Boards
</Link>
</>
)}
</SignedOut>
<SignedIn>
<Link href="/boards/new" className="btn-large white">
Create Board
</Link>
<Link href="/boards" className="btn-large outline">
View Boards
</Link>
</SignedIn>
</div>
</div>
</section>
</>
);
}

View File

@@ -1,13 +1,25 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { SignOutButton, useUser } from "@/auth/clerk";
import { LogOut } from "lucide-react";
import {
Activity,
Bot,
ChevronDown,
LayoutDashboard,
LogOut,
Plus,
Server,
Trello,
} from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
export function UserMenu({ className }: { className?: string }) {
const [open, setOpen] = useState(false);
const { user } = useUser();
if (!user) return null;
@@ -19,39 +31,59 @@ export function UserMenu({ className }: { className?: string }) {
const displayEmail = user.primaryEmailAddress?.emailAddress ?? "";
return (
<Popover>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
className={cn(
"flex h-11 items-center rounded-lg border border-transparent px-1 text-slate-900 transition hover:border-slate-200 hover:bg-slate-50",
"group inline-flex h-9 items-center gap-2 rounded-[10px] bg-transparent px-1 py-1 transition",
"hover:bg-white/70",
// Avoid the default browser focus outline (often bright blue) on click.
// Keep a subtle, enterprise-looking focus ring for keyboard navigation.
"focus:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--neutral-300,var(--border-strong))] focus-visible:ring-offset-2 focus-visible:ring-offset-white",
"data-[state=open]:bg-white",
className,
)}
aria-label="Open user menu"
>
<span className="flex h-11 w-11 items-center justify-center overflow-hidden rounded-lg bg-slate-100 text-sm font-semibold text-slate-900 shadow-sm">
<span
className={cn(
"relative flex h-9 w-9 items-center justify-center overflow-hidden rounded-[10px] text-xs font-semibold text-white shadow-sm",
avatarUrl
? "bg-[color:var(--neutral-200,var(--surface-muted))]"
: "bg-gradient-to-br from-[color:var(--primary-navy,var(--accent))] to-[color:var(--secondary-navy,var(--accent-strong))]",
)}
>
{avatarUrl ? (
<Image
src={avatarUrl}
alt="User avatar"
width={44}
height={44}
className="h-11 w-11 object-cover"
width={36}
height={36}
className="h-9 w-9 object-cover"
/>
) : (
avatarLabel
)}
</span>
<ChevronDown className="h-4 w-4 text-[color:var(--neutral-700,var(--text-quiet))] transition group-data-[state=open]:rotate-180" />
</button>
</PopoverTrigger>
<PopoverContent
align="end"
sideOffset={10}
className="w-64 rounded-2xl border border-slate-200 bg-white p-0 shadow-lg"
sideOffset={12}
className="w-80 overflow-hidden rounded-2xl border border-[color:var(--neutral-200,var(--border))] bg-white/95 p-0 shadow-[0_8px_32px_rgba(10,22,40,0.08)] backdrop-blur"
>
<div className="border-b border-slate-200 px-4 py-3">
<div className="border-b border-[color:var(--neutral-200,var(--border))] px-4 py-3">
<div className="flex items-center gap-3">
<span className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-lg bg-slate-100 text-sm font-semibold text-slate-900">
<span
className={cn(
"flex h-10 w-10 items-center justify-center overflow-hidden rounded-xl text-sm font-semibold text-white",
avatarUrl
? "bg-[color:var(--neutral-200,var(--surface-muted))]"
: "bg-gradient-to-br from-[color:var(--primary-navy,var(--accent))] to-[color:var(--secondary-navy,var(--accent-strong))]",
)}
>
{avatarUrl ? (
<Image
src={avatarUrl}
@@ -65,22 +97,67 @@ export function UserMenu({ className }: { className?: string }) {
)}
</span>
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-slate-900">
<div className="truncate text-sm font-semibold text-[color:var(--primary-navy,var(--text))]">
{displayName}
</div>
{displayEmail ? (
<div className="truncate text-xs text-slate-500">{displayEmail}</div>
<div className="truncate text-xs text-[color:var(--neutral-700,var(--text-muted))]">
{displayEmail}
</div>
) : null}
</div>
</div>
</div>
<div className="p-2">
<div className="grid grid-cols-2 gap-2">
<Link
href="/boards"
className="flex w-full items-center justify-center gap-2 rounded-xl border border-[color:var(--neutral-300,var(--border-strong))] bg-white px-3 py-2 text-sm font-semibold text-[color:var(--neutral-800,var(--text))] transition hover:border-[color:var(--primary-navy,var(--accent-strong))] hover:bg-[color:var(--neutral-100,var(--surface-muted))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-teal,var(--accent))] focus-visible:ring-offset-2"
onClick={() => setOpen(false)}
>
<Trello className="h-4 w-4 text-[color:var(--neutral-700,var(--text-quiet))]" />
Open boards
</Link>
<Link
href="/boards/new"
className="flex w-full items-center justify-center gap-2 rounded-xl bg-[color:var(--primary-navy,var(--accent))] px-3 py-2 text-sm font-semibold text-white shadow-[0_2px_8px_rgba(10,22,40,0.15)] transition hover:bg-[color:var(--secondary-navy,var(--accent-strong))] hover:translate-y-[-1px] hover:shadow-[0_4px_12px_rgba(10,22,40,0.20)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-teal,var(--accent))] focus-visible:ring-offset-2"
onClick={() => setOpen(false)}
>
<Plus className="h-4 w-4 opacity-90" />
Create board
</Link>
</div>
<div className="my-2 h-px bg-[color:var(--neutral-200,var(--border))]" />
{(
[
{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
{ href: "/activity", label: "Activity", icon: Activity },
{ href: "/agents", label: "Agents", icon: Bot },
{ href: "/gateways", label: "Gateways", icon: Server },
] as const
).map((item) => (
<Link
key={item.href}
href={item.href}
className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-[color:var(--neutral-800,var(--text))] transition hover:bg-[color:var(--neutral-100,var(--surface-muted))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-teal,var(--accent))] focus-visible:ring-offset-2"
onClick={() => setOpen(false)}
>
<item.icon className="h-4 w-4 text-[color:var(--neutral-700,var(--text-quiet))]" />
{item.label}
</Link>
))}
<div className="my-2 h-px bg-[color:var(--neutral-200,var(--border))]" />
<SignOutButton>
<button
type="button"
className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-slate-900 transition hover:bg-slate-100"
className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-[color:var(--neutral-800,var(--text))] transition hover:bg-[color:var(--neutral-100,var(--surface-muted))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-teal,var(--accent))] focus-visible:ring-offset-2"
onClick={() => setOpen(false)}
>
<LogOut className="h-4 w-4 text-slate-500" />
<LogOut className="h-4 w-4 text-[color:var(--neutral-700,var(--text-quiet))]" />
Sign out
</button>
</SignOutButton>