Add homepage social proof
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -38,7 +38,7 @@ import {
|
||||
getDownloadOptions,
|
||||
useDetectedPlatform,
|
||||
AppleIcon,
|
||||
AndroidIcon,
|
||||
PlayStoreIcon,
|
||||
TerminalIcon,
|
||||
GlobeIcon,
|
||||
} from "~/downloads";
|
||||
@@ -87,6 +87,7 @@ export function LandingPage({ title, subtitle }: LandingPageProps) {
|
||||
<div className="bg-background">
|
||||
<main className="p-6 md:p-20 md:pt-40 max-w-5xl mx-auto">
|
||||
<div className="space-y-24">
|
||||
<SocialProofWall />
|
||||
<MultiProviderSection />
|
||||
<SelfHostedSection />
|
||||
<ServiceProxySection />
|
||||
@@ -129,6 +130,88 @@ const PI_BADGE_ICON = <PiIcon className="h-6 w-6" />;
|
||||
const FEATURED_AGENT_COUNT = 5;
|
||||
const ADDITIONAL_AGENT_COUNT = AGENT_PAGES.length - FEATURED_AGENT_COUNT;
|
||||
|
||||
const SOCIAL_PROOF_TWEETS = [
|
||||
{
|
||||
name: "Cam",
|
||||
handle: "@ceeebeeebeee",
|
||||
date: "Apr 6, 2026",
|
||||
avatar: "/social-proof/ceeebeeebeee.jpg",
|
||||
url: "https://x.com/ceeebeeebeee/status/2041008798798864537",
|
||||
text: "without a doubt the most slept on orchestrator right now. Open source, every OS, and a mobile experience that truly blew me away.",
|
||||
},
|
||||
{
|
||||
name: "Erik Sherman",
|
||||
handle: "@erikksherman",
|
||||
date: "Apr 11, 2026",
|
||||
avatar: "/social-proof/erikksherman.jpg",
|
||||
url: "https://x.com/erikksherman/status/2043011630590751008",
|
||||
text: "control agents from anywhere - mac, phone, web. one simple change transformed my health while INCREASING productivity",
|
||||
},
|
||||
{
|
||||
name: "Aman Kumar Jagdev",
|
||||
handle: "@amankumarjagdev",
|
||||
date: "Apr 16, 2026",
|
||||
avatar: "/social-proof/amankumarjagdev.jpg",
|
||||
url: "https://x.com/amankumarjagdev/status/2044815258414674307",
|
||||
text: "I have tried 100s of agent orchestrator, cli and gui. the best one i have found. Please give it a try! it's really good",
|
||||
},
|
||||
{
|
||||
name: "RUI",
|
||||
handle: "@tietougongshiba",
|
||||
date: "May 3, 2026",
|
||||
avatar: "/social-proof/tietougongshiba.jpg",
|
||||
url: "https://x.com/tietougongshiba/status/2050886374941925754",
|
||||
text: "Being able to check and manage agent progress from my phone while I'm out is so convenient.",
|
||||
},
|
||||
{
|
||||
name: "Jason Torres",
|
||||
handle: "@jasontorres",
|
||||
date: "May 11, 2026",
|
||||
avatar: "/social-proof/jasontorres.jpg",
|
||||
url: "https://x.com/jasontorres/status/2053875385515790731",
|
||||
text: "Can interchange between Codex, Claude Code, Opencode, Pi. Stable mobile and desktop apps connected through a secure relay from your VMs.",
|
||||
},
|
||||
{
|
||||
name: "A9",
|
||||
handle: "@aadtyn",
|
||||
date: "May 29, 2026",
|
||||
avatar: "/social-proof/aadtyn.jpg",
|
||||
url: "https://x.com/aadtyn/status/2060371229773803943",
|
||||
text: "cross platform agent orchestration with inbuilt relay and tailscale / self host daemon options + the best UI ive seen in this segment",
|
||||
},
|
||||
{
|
||||
name: "boris evstratov",
|
||||
handle: "@bevstratov",
|
||||
date: "May 30, 2026",
|
||||
avatar: "/social-proof/bevstratov.jpg",
|
||||
url: "https://x.com/bevstratov/status/2060733983042781550",
|
||||
text: "It’s an incredible piece of software. The last building block I needed to fully work from my phone. everything super smooth.",
|
||||
},
|
||||
{
|
||||
name: "Arnold Gamboa",
|
||||
handle: "@arnoldgamboa",
|
||||
date: "May 28, 2026",
|
||||
avatar: "/social-proof/arnoldgamboa.jpg",
|
||||
url: "https://x.com/arnoldgamboa/status/2059832028099436921",
|
||||
text: "Paseo is a really good interface for Pi. It’s not the only thing it does, but that’s my current use case for now.",
|
||||
},
|
||||
{
|
||||
name: "Dong",
|
||||
handle: "@dongnaebi",
|
||||
date: "Apr 12, 2026",
|
||||
avatar: "/social-proof/dongnaebi.jpg",
|
||||
url: "https://x.com/dongnaebi/status/2043162391941398735",
|
||||
text: "Paseo is the best software I've used this year. Absolutely amazing!",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const SOCIAL_PROOF_ROWS = [
|
||||
{ id: "top", tweets: SOCIAL_PROOF_TWEETS.slice(0, 5), reverse: false },
|
||||
{ id: "bottom", tweets: SOCIAL_PROOF_TWEETS.slice(5), reverse: true },
|
||||
] as const;
|
||||
|
||||
type SocialProofTweet = (typeof SOCIAL_PROOF_TWEETS)[number];
|
||||
|
||||
function AgentBadge({ name, icon }: { name: string; icon: React.ReactNode }) {
|
||||
const [hovered, setHovered] = React.useState(false);
|
||||
const handleMouseEnter = React.useCallback(() => setHovered(true), []);
|
||||
@@ -173,17 +256,100 @@ function FeatureSection({
|
||||
whileInView={FADE_IN}
|
||||
viewport={VIEWPORT_60}
|
||||
transition={EASE_OUT_05}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-3xl font-medium">{title}</h2>
|
||||
<p className="text-base text-muted-foreground max-w-lg">{description}</p>
|
||||
</div>
|
||||
<SectionTitle title={title} description={description} />
|
||||
{children}
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionTitle({ title, description }: { title: string; description: string }) {
|
||||
return (
|
||||
<div className="mb-12 space-y-2">
|
||||
<h2 className="text-3xl font-medium">{title}</h2>
|
||||
<p className="text-base text-muted-foreground max-w-lg">{description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SocialProofWall() {
|
||||
return (
|
||||
<motion.section
|
||||
initial={FADE_IN_UP}
|
||||
whileInView={FADE_IN}
|
||||
viewport={VIEWPORT_60}
|
||||
transition={EASE_OUT_05}
|
||||
>
|
||||
<SectionTitle
|
||||
title="Loved by developers"
|
||||
description="See what developers are saying about Paseo."
|
||||
/>
|
||||
|
||||
<div className="social-proof-marquee space-y-4 overflow-hidden">
|
||||
{SOCIAL_PROOF_ROWS.map((row) => (
|
||||
<SocialProofRow key={row.id} tweets={row.tweets} reverse={row.reverse} />
|
||||
))}
|
||||
</div>
|
||||
</motion.section>
|
||||
);
|
||||
}
|
||||
|
||||
function SocialProofRow({
|
||||
tweets,
|
||||
reverse,
|
||||
}: {
|
||||
tweets: readonly SocialProofTweet[];
|
||||
reverse: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="social-proof-row">
|
||||
<div className={`social-proof-track ${reverse ? "social-proof-track-reverse" : ""}`}>
|
||||
<div className="flex shrink-0 gap-4 px-2">
|
||||
{tweets.map((tweet) => (
|
||||
<SocialProofCard key={tweet.url} tweet={tweet} />
|
||||
))}
|
||||
</div>
|
||||
<div className="flex shrink-0 gap-4 px-2" aria-hidden="true">
|
||||
{tweets.map((tweet) => (
|
||||
<SocialProofCard key={`${tweet.url}-clone`} tweet={tweet} inert />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SocialProofCard({ tweet, inert }: { tweet: SocialProofTweet; inert?: boolean }) {
|
||||
return (
|
||||
<a
|
||||
href={tweet.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
tabIndex={inert ? -1 : undefined}
|
||||
className="group flex h-[154px] w-[320px] shrink-0 flex-col justify-between overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] p-4 transition-colors hover:border-white/20 hover:bg-white/[0.05] md:w-[420px]"
|
||||
aria-label={`Read ${tweet.name}'s original post`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<img
|
||||
src={tweet.avatar}
|
||||
alt=""
|
||||
width={28}
|
||||
height={28}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-7 w-7 shrink-0 rounded-full bg-white/10 object-cover"
|
||||
/>
|
||||
<p className="truncate text-sm font-medium text-white/60">{tweet.handle}</p>
|
||||
</div>
|
||||
<p className="social-proof-card-text mt-4 text-sm leading-relaxed text-white/72">
|
||||
{tweet.text}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function MultiProviderSection() {
|
||||
const providers = [
|
||||
{ name: "Claude Code", icon: <ClaudeIcon size={28} /> },
|
||||
@@ -198,19 +364,8 @@ function MultiProviderSection() {
|
||||
title="Use the best agent for the job"
|
||||
description="Run multiple providers from a single interface. Paseo runs the native agent harness as you'd normally run it, with your skills, config and MCP servers intact."
|
||||
>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{providers.slice(0, 3).map((p) => (
|
||||
<div
|
||||
key={p.name}
|
||||
className="flex items-center justify-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-5 py-4"
|
||||
>
|
||||
<span className="text-white/80">{p.icon}</span>
|
||||
<span className="font-medium">{p.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4 sm:w-full">
|
||||
{providers.slice(3).map((p) => (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
{providers.map((p) => (
|
||||
<div
|
||||
key={p.name}
|
||||
className="flex items-center justify-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-5 py-4"
|
||||
@@ -877,7 +1032,7 @@ function GetStarted() {
|
||||
className="inline-flex items-center justify-center rounded-lg border border-white/20 px-3 py-2 text-white hover:bg-white/10 transition-colors"
|
||||
aria-label="Google Play"
|
||||
>
|
||||
<AndroidIcon className="h-5 w-5" />
|
||||
<PlayStoreIcon className="h-5 w-5" />
|
||||
</a>
|
||||
<ServerInstallButton />
|
||||
</div>
|
||||
@@ -1422,7 +1577,7 @@ function CLISection() {
|
||||
title="Fully scriptable"
|
||||
description="Everything you can do in the app, you can do from the terminal."
|
||||
>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="mb-3 flex flex-wrap gap-2">
|
||||
{cliExamples.map((example, i) => (
|
||||
<CLITabButton
|
||||
key={example.title}
|
||||
@@ -1434,7 +1589,7 @@ function CLISection() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="mb-3">
|
||||
<CLICodeBlock>{active.code}</CLICodeBlock>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -120,6 +120,26 @@ export function AndroidIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayStoreIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M3.27 2.17c-.2.21-.32.55-.32 1v17.66c0 .45.12.79.33 1l9.87-9.84z" fill="#00F076" />
|
||||
<path
|
||||
d="m16.43 8.72-3.28 3.27 3.29 3.28 3.9-2.22c1.13-.64 1.13-1.42 0-2.06z"
|
||||
fill="#FFCE00"
|
||||
/>
|
||||
<path d="m13.15 11.99 3.28-3.27L4.63 2.01c-.56-.32-1.04-.28-1.36.16z" fill="#00D6FF" />
|
||||
<path d="M3.28 21.83c.32.44.8.48 1.36.16l11.8-6.72-3.29-3.28z" fill="#FF3A44" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function WindowsIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -9,6 +9,55 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes social-proof-scroll {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.social-proof-marquee {
|
||||
margin-inline: calc(50% - 50vw);
|
||||
mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
|
||||
overflow-x: clip;
|
||||
padding-inline: max(1.5rem, calc((100vw - 80rem) / 2));
|
||||
}
|
||||
|
||||
.social-proof-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.social-proof-track {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
animation: social-proof-scroll 46s linear infinite;
|
||||
}
|
||||
|
||||
.social-proof-track-reverse {
|
||||
animation-direction: reverse;
|
||||
animation-duration: 52s;
|
||||
}
|
||||
|
||||
.social-proof-marquee:hover .social-proof-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.social-proof-card-text {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.social-proof-track {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Title font - centralized for easy changes */
|
||||
.font-title {
|
||||
font-family: inherit;
|
||||
|
||||