Files
podman/frontend/src/Landing.tsx
T
Karti Tripathi 26c851e2bb feat(frontend): add podman.live landing page + route split
Add a public marketing landing at "/" celebrating the Top-3 finish at the
AI Engineer World's Fair 2026 hackathon (2nd overall of 95+ teams, LiveKit
track + DigitalOcean Best Use), with a scroll-to-read hero, feature/tech
sections, and the demo embed.

Introduce react-router: "/" serves the public landing (no auth) and "/app"
mounts the existing Clerk-gated app. The app route degrades gracefully to a
"deploying" placeholder when no Clerk publishable key is configured, so the
site renders without secrets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 03:15:36 +00:00

275 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback } from 'react';
import { Link } from 'react-router-dom';
import {
TrophyIcon,
ArrowRightIcon,
ChevronDownIcon,
EyeIcon,
GitMergeIcon,
UsersIcon,
BellIcon,
RadioIcon,
DatabaseIcon,
SparklesIcon,
PlayIcon,
MicIcon,
} from 'lucide-react';
/**
* Public marketing landing page for podman.live.
* Rendered at "/" without ClerkProvider — needs no secrets.
* The live app lives at "/app".
*/
export function Landing() {
const scrollToStory = useCallback(() => {
document.getElementById('story')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, []);
return (
<div className="min-h-screen scroll-smooth bg-[#0b0f17] text-white/90 antialiased">
{/* ambient glows */}
<div className="pointer-events-none fixed inset-0 overflow-hidden">
<div className="absolute -top-40 left-1/2 h-[38rem] w-[38rem] -translate-x-1/2 rounded-full bg-indigo-600/20 blur-[120px]" />
<div className="absolute top-1/3 -right-32 h-[26rem] w-[26rem] rounded-full bg-cyan-500/10 blur-[110px]" />
</div>
{/* top nav */}
<header className="relative z-20 mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
<a href="#top" className="flex items-center gap-2.5">
<img src="/podman-logo.svg" alt="PodMan" className="h-7 w-7" />
<span className="text-[15px] font-semibold tracking-tight text-white">PodMan</span>
</a>
<Link
to="/app"
className="inline-flex items-center gap-1.5 rounded-full border border-white/15 bg-white/5 px-4 py-1.5 text-sm font-medium text-white/90 transition-colors hover:bg-white/10"
>
Launch app <ArrowRightIcon className="size-4" />
</Link>
</header>
{/* hero */}
<section id="top" className="relative z-10 mx-auto flex max-w-3xl flex-col items-center px-6 pt-16 pb-24 text-center sm:pt-24">
<button
onClick={scrollToStory}
className="group mb-8 inline-flex items-center gap-2 rounded-full border border-amber-300/30 bg-gradient-to-r from-amber-400/15 to-amber-200/5 px-4 py-1.5 text-sm font-medium text-amber-200 transition-colors hover:border-amber-300/50"
>
<TrophyIcon className="size-4" />
Top 3 AI Engineer World&apos;s Fair Hackathon 2026
<ChevronDownIcon className="size-4 transition-transform group-hover:translate-y-0.5" />
</button>
<h1 className="text-5xl font-semibold leading-[1.05] tracking-tight text-white sm:text-7xl">
PodMan
</h1>
<p className="mt-5 bg-gradient-to-r from-indigo-300 via-sky-300 to-cyan-200 bg-clip-text text-2xl font-medium text-transparent sm:text-3xl">
The teammate that sees what git can&apos;t.
</p>
<p className="mx-auto mt-6 max-w-xl text-base leading-relaxed text-white/60 sm:text-lg">
An ambient AI teammate that watches your pod&apos;s screens in realtime catching merge
collisions, duplicated work, and missed handoffs before anyone pushes.
</p>
{/* award chips */}
<div className="mt-9 flex flex-wrap items-center justify-center gap-2.5">
{[
{ label: '2nd Overall · 95+ teams', accent: 'text-amber-200 border-amber-300/25 bg-amber-400/10' },
{ label: 'LiveKit Track — Winner', accent: 'text-sky-200 border-sky-300/25 bg-sky-400/10' },
{ label: 'DigitalOcean — Best Use', accent: 'text-cyan-200 border-cyan-300/25 bg-cyan-400/10' },
].map((c) => (
<span
key={c.label}
className={`rounded-full border px-3.5 py-1.5 text-[13px] font-medium ${c.accent}`}
>
{c.label}
</span>
))}
</div>
{/* CTAs */}
<div className="mt-10 flex flex-col items-center gap-3 sm:flex-row">
<button
onClick={scrollToStory}
className="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-gradient-to-r from-indigo-500 to-cyan-400 px-7 text-sm font-semibold text-[#0b0f17] shadow-lg shadow-indigo-500/20 transition-transform hover:scale-[1.02]"
>
Read the story <ChevronDownIcon className="size-4" />
</button>
<Link
to="/app"
className="inline-flex h-11 items-center justify-center gap-2 rounded-full border border-white/15 px-7 text-sm font-semibold text-white/90 transition-colors hover:bg-white/5"
>
Launch PodMan <ArrowRightIcon className="size-4" />
</Link>
</div>
<button
onClick={scrollToStory}
aria-label="Scroll to read"
className="mt-20 animate-bounce text-white/40 transition-colors hover:text-white/70"
>
<ChevronDownIcon className="size-7" />
</button>
</section>
{/* story */}
<Section id="story" eyebrow="How it happened" title="Built on stage, in about 24 hours.">
<div className="space-y-5 text-[15px] leading-relaxed text-white/70">
<p>
PodMan was built at the{' '}
<span className="text-white">AI Engineer World&apos;s Fair 2026 Hackathon</span> (June
2728) against 95+ teams. It took{' '}
<span className="text-amber-200">2nd place overall</span>, and won both the{' '}
<span className="text-sky-200">LiveKit</span> track and{' '}
<span className="text-cyan-200">DigitalOcean&apos;s Best Use</span> award.
</p>
<p>
We shipped the live demo in a single sprint a LiveKit room, Gemini watching the
screen-share, and a voice that speaks up the moment two engineers drift toward the same
code. There wasn&apos;t time on the clock to give it a proper home.
</p>
<p className="text-white/90">
So this is that home and we&apos;re actively building PodMan out from the hackathon
prototype into something a team can actually run every day.
</p>
</div>
</Section>
{/* what it does */}
<Section id="what" eyebrow="What it does" title="A teammate in the room, not another dashboard.">
<div className="grid gap-4 sm:grid-cols-2">
<Feature
icon={<EyeIcon className="size-5" />}
title="Ambient awareness"
body="Joins your pod's LiveKit room and samples every screen-share in realtime — no bots to invoke, no commands to remember."
/>
<Feature
icon={<GitMergeIcon className="size-5" />}
title="Merge-collision radar"
body="Recognizes when two people are converging on the same files or feature and warns them before the conflict ever reaches a push."
/>
<Feature
icon={<UsersIcon className="size-5" />}
title="Duplicated-work detection"
body="Notices when effort overlaps across the team and surfaces it early, so two engineers don't quietly build the same thing."
/>
<Feature
icon={<BellIcon className="size-5" />}
title="Handoff nudges"
body="Speaks up about missed handoffs and stalled threads — and learns which nudges actually helped, so it stays useful, not noisy."
/>
</div>
</Section>
{/* how it works */}
<Section id="how" eyebrow="Under the hood" title="Realtime vision, memory, and a voice.">
<div className="grid gap-4 sm:grid-cols-3">
<Stack icon={<RadioIcon className="size-5" />} name="LiveKit" note="Realtime transport for the shared room and voice delivery." />
<Stack icon={<SparklesIcon className="size-5" />} name="Gemini" note="Vision + event detection + the live voice that nudges the team." />
<Stack icon={<DatabaseIcon className="size-5" />} name="MongoDB" note="Engineer state, ownership map, events, and which nudges landed." />
</div>
<div className="mt-6 flex flex-wrap gap-2 text-[13px] text-white/50">
{['LiveKit', 'Gemini Live', 'MongoDB Atlas', 'Voyage embeddings', 'Modular MAX', 'Hermes ops'].map((t) => (
<span key={t} className="rounded-md border border-white/10 bg-white/[0.03] px-2.5 py-1">
{t}
</span>
))}
</div>
</Section>
{/* demo */}
<Section id="demo" eyebrow="See it" title="The 60-second demo.">
<div className="overflow-hidden rounded-2xl border border-white/10 bg-black/40 shadow-2xl">
<div className="relative w-full" style={{ paddingBottom: '56.25%' }}>
<iframe
className="absolute inset-0 h-full w-full"
src="https://www.youtube-nocookie.com/embed/bWJIsIWTgr0"
title="PodMan demo"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
</div>
<div className="mt-4 flex items-center gap-2 text-sm text-white/40">
<PlayIcon className="size-4" /> Watch the pod get its warning before the collision.
</div>
</Section>
{/* final CTA */}
<section className="relative z-10 mx-auto max-w-3xl px-6 py-24 text-center">
<div className="rounded-3xl border border-white/10 bg-gradient-to-b from-white/[0.06] to-transparent px-8 py-14">
<MicIcon className="mx-auto size-8 text-cyan-300" />
<h2 className="mt-5 text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Put PodMan in your pod.
</h2>
<p className="mx-auto mt-4 max-w-md text-[15px] leading-relaxed text-white/60">
Open a room and let it watch the work. It&apos;s an early build we&apos;re actively
shaping jump in and try it.
</p>
<Link
to="/app"
className="mt-8 inline-flex h-12 items-center justify-center gap-2 rounded-full bg-gradient-to-r from-indigo-500 to-cyan-400 px-8 text-sm font-semibold text-[#0b0f17] shadow-lg shadow-indigo-500/20 transition-transform hover:scale-[1.02]"
>
Launch PodMan <ArrowRightIcon className="size-4" />
</Link>
</div>
</section>
{/* footer */}
<footer className="relative z-10 border-t border-white/10">
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-3 px-6 py-8 text-sm text-white/40 sm:flex-row">
<div className="flex items-center gap-2">
<img src="/podman-logo.svg" alt="" className="h-5 w-5 opacity-70" />
<span>PodMan · podman.live</span>
</div>
<span>Built at AI Engineer World&apos;s Fair 2026 · 2nd Overall</span>
</div>
</footer>
</div>
);
}
function Section({
id,
eyebrow,
title,
children,
}: {
id: string;
eyebrow: string;
title: string;
children: React.ReactNode;
}) {
return (
<section id={id} className="relative z-10 border-t border-white/[0.06] py-20">
<div className="mx-auto max-w-3xl px-6">
<p className="text-sm font-medium tracking-wide text-cyan-300/80 uppercase">{eyebrow}</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-white sm:text-4xl">{title}</h2>
<div className="mt-8">{children}</div>
</div>
</section>
);
}
function Feature({ icon, title, body }: { icon: React.ReactNode; title: string; body: string }) {
return (
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6 transition-colors hover:border-white/20">
<div className="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500/20 to-cyan-400/20 text-cyan-200">
{icon}
</div>
<h3 className="mt-4 text-lg font-semibold text-white">{title}</h3>
<p className="mt-2 text-[14px] leading-relaxed text-white/60">{body}</p>
</div>
);
}
function Stack({ icon, name, note }: { icon: React.ReactNode; name: string; note: string }) {
return (
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center gap-2.5 text-white">
<span className="text-cyan-200">{icon}</span>
<span className="text-base font-semibold">{name}</span>
</div>
<p className="mt-3 text-[14px] leading-relaxed text-white/60">{note}</p>
</div>
);
}