Add homepage social proof

This commit is contained in:
Mohamed Boudra
2026-06-01 14:06:12 +07:00
parent 7fb3dda20c
commit 539d2969f1
12 changed files with 246 additions and 22 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

+177 -22
View File
@@ -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>
+20
View File
@@ -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
+49
View File
@@ -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;