Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/blog/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/global.css",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/global.css",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
Expand Down
4 changes: 2 additions & 2 deletions apps/eclipse/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/globals.css",
"config": "",
"css": "src/app/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
Expand Down
2 changes: 1 addition & 1 deletion apps/site/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/global.css",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
Expand Down
42 changes: 42 additions & 0 deletions apps/site/src/app/mcp/_components/agent-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Image from "next/image";
import type { LucideIcon } from "lucide-react";

export function AgentCard({
logo,
alt,
icon: Icon,
href,
}: {
logo: string | null;
alt: string;
icon: LucideIcon;
href: string;
}) {
return (
<a
href={href}
title={alt}
aria-label={alt}
className="group relative flex h-[120px] w-full items-center justify-center rounded-[12px] border border-stroke-neutral bg-background-neutral-weaker no-underline outline-offset-4 transition-[border-color,background-color] hover:border-stroke-ppg/60 hover:bg-background-neutral focus-visible:ring-2 focus-visible:ring-stroke-ppg"
>
{logo ? (
<Image
src={logo}
alt=""
width={48}
height={48}
className="size-12 object-contain opacity-55 grayscale transition-opacity group-hover:opacity-80 dark:brightness-0 dark:invert"
unoptimized
/>
) : (
<span className="font-mono text-lg text-foreground-neutral-weak">Any AI agent</span>
)}
<span
className="absolute right-[7px] top-[7px] text-foreground-neutral-weaker opacity-60 transition-opacity group-hover:opacity-100"
aria-hidden
>
<Icon className="size-3.5" strokeWidth={1.75} />
</span>
</a>
);
}
87 changes: 87 additions & 0 deletions apps/site/src/app/mcp/_components/capability-cards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { LucideIcon } from "lucide-react";

import { McpBubble } from "./mcp-bubble";

const capabilityIconClass = "size-6 text-foreground-ppg shrink-0";

export function MobileCapabilityCard({
icon: Icon,
title,
description,
prompt,
mobileTall,
}: {
icon: LucideIcon;
title: string;
description: string;
prompt: string;
mobileTall: boolean;
}) {
return (
<div
className={`flex w-full flex-col overflow-hidden rounded-[12px] border border-stroke-neutral/40 bg-background-default shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:border-white/8 dark:bg-[radial-gradient(circle_at_top_left,rgba(10,112,100,0.10),transparent_28%),linear-gradient(180deg,#08111c_0%,#060d18_100%)] ${
mobileTall ? "min-h-[227px]" : "min-h-[203px]"
}`}
>
<div className="flex flex-col gap-4 p-4">
<div className="flex items-center gap-4">
<div className="flex size-12 shrink-0 items-center justify-center rounded-[6px] bg-background-ppg">
<Icon className={capabilityIconClass} strokeWidth={1.75} aria-hidden />
</div>
<h4 className="font-sans-display text-[20px] leading-7 font-extrabold text-foreground-neutral">
{title}
</h4>
</div>
<p className="max-w-full text-[16px] leading-6 text-foreground-neutral-weak">
{description}
</p>
</div>
<div className="mt-auto px-3.5 pb-3.5">
<McpBubble variant={mobileTall ? "prompt-mobile-tall" : "prompt-mobile"}>
{prompt}
</McpBubble>
</div>
</div>
);
}

export function CapabilityCard({
icon: Icon,
title,
description,
prompt,
size,
}: {
icon: LucideIcon;
title: string;
description: string;
prompt: string;
size: "wide" | "compact";
}) {
const isWide = size === "wide";

return (
<div
className={`flex w-full flex-col overflow-hidden rounded-[12px] border border-stroke-neutral/40 bg-background-default shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:border-white/8 dark:bg-[radial-gradient(circle_at_top_left,rgba(10,112,100,0.10),transparent_28%),linear-gradient(180deg,#08111c_0%,#060d18_100%)] ${
isWide ? "md:min-h-[179px] md:w-[590px]" : "md:min-h-[203px] md:w-[389px]"
}`}
>
<div className="flex flex-col gap-4 p-4">
<div className="flex items-center gap-4">
<div className="flex size-12 shrink-0 items-center justify-center rounded-[6px] bg-background-ppg">
<Icon className={capabilityIconClass} strokeWidth={1.75} aria-hidden />
</div>
<h4 className="font-sans-display text-[20px] leading-7 font-extrabold text-foreground-neutral">
{title}
</h4>
</div>
<p className="max-w-full text-[16px] leading-6 text-foreground-neutral-weak">
{description}
</p>
</div>
<div className="mt-auto px-4 pb-4">
<McpBubble variant={isWide ? "prompt-wide" : "prompt-compact"}>{prompt}</McpBubble>
</div>
</div>
);
}
12 changes: 12 additions & 0 deletions apps/site/src/app/mcp/_components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export { AgentCard } from "./agent-card";
export { CapabilityCard, MobileCapabilityCard } from "./capability-cards";
export type { McpAgent } from "./mcp-agents-section";
export { McpAgentsSection } from "./mcp-agents-section";
export type { McpBubbleVariant } from "./mcp-bubble";
export { McpBubble } from "./mcp-bubble";
export type { McpCapability } from "./mcp-capabilities-section";
export { McpCapabilitiesSection } from "./mcp-capabilities-section";
export { McpCtaSection } from "./mcp-cta-section";
export type { McpHeroFeature } from "./mcp-hero-section";
export { McpHeroSection } from "./mcp-hero-section";
export { McpVideoSection } from "./mcp-video-section";
49 changes: 49 additions & 0 deletions apps/site/src/app/mcp/_components/mcp-agents-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Button } from "@prisma/eclipse";
import type { LucideIcon } from "lucide-react";

import { AgentCard } from "./agent-card";

export type McpAgent = {
logo: string | null;
alt: string;
icon: LucideIcon;
href: string;
};

export function McpAgentsSection({
docsHref,
agents,
}: {
docsHref: string;
agents: readonly McpAgent[];
}) {
return (
<section className="px-4 py-12 md:px-0">
<div className="mx-auto flex max-w-[790px] flex-col items-center gap-12 text-center">
<div className="flex max-w-[768px] flex-col items-center gap-4">
<h2 className="font-sans-display text-4xl font-black text-foreground-neutral md:text-5xl">
Works with your AI agent
</h2>
<p className="text-base leading-6 text-foreground-neutral-weak">
Works with any AI agent, whether you prefer to use a remote or a local server,
we&apos;ve got you.
</p>
</div>

<div className="grid w-full max-w-[368px] grid-cols-2 gap-4 md:max-w-[790px] md:grid-cols-4 md:gap-8">
{agents.map(({ logo, alt, icon, href }) => (
<AgentCard key={alt} logo={logo} alt={alt} icon={icon} href={href} />
))}
</div>

<Button
href={docsHref}
variant={"link"}
className="text-sm font-semibold text-foreground-ppg underline"
>
Want to see your tool listed?
</Button>
</div>
</section>
);
}
94 changes: 94 additions & 0 deletions apps/site/src/app/mcp/_components/mcp-bubble.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ReactNode } from "react";

const fillTitle = "#030712";
const fillTeal = "#042F2E";

export type McpBubbleVariant =
| "hero-desktop-title"
| "hero-desktop-description"
| "hero-mobile-title"
| "hero-mobile-description"
| "prompt-mobile"
| "prompt-mobile-tall"
| "prompt-wide"
| "prompt-compact";

type BubbleConfig = {
shell: string;
fill: string;
monoPrompt: boolean;
};

const config: Record<McpBubbleVariant, BubbleConfig> = {
"hero-desktop-title": {
fill: fillTitle,
monoPrompt: false,
shell:
"min-h-[67px] items-center justify-center rounded-xl border border-stroke-ppg px-6 py-3 md:min-h-[67px] md:px-8",
},
"hero-desktop-description": {
fill: fillTeal,
monoPrompt: false,
shell:
"min-h-[72px] items-center rounded-xl border border-stroke-ppg px-6 py-3 md:min-h-[72px] md:px-6",
},
"hero-mobile-title": {
fill: fillTitle,
monoPrompt: false,
shell:
"min-h-[120px] items-center justify-center rounded-xl border border-stroke-ppg px-6 py-6 sm:min-h-[128px]",
},
"hero-mobile-description": {
fill: fillTeal,
monoPrompt: false,
shell:
"min-h-[108px] items-center rounded-xl border border-stroke-ppg px-4 py-5 sm:min-h-[112px]",
},
"prompt-mobile": {
fill: fillTeal,
monoPrompt: true,
shell:
"items-center rounded-[10px] border border-stroke-ppg px-3.5 py-2 shadow-none sm:px-4 min-h-[48px]",
},
"prompt-mobile-tall": {
fill: fillTeal,
monoPrompt: true,
shell:
"items-center rounded-[10px] border border-stroke-ppg px-3.5 py-2.5 shadow-none sm:px-4 min-h-[64px] sm:min-h-[72px]",
},
"prompt-wide": {
fill: fillTeal,
monoPrompt: true,
shell:
"items-center rounded-[10px] border border-stroke-ppg px-3.5 py-2 shadow-none sm:px-4 min-h-[41px] py-2 md:min-h-[45px]",
},
"prompt-compact": {
fill: fillTeal,
monoPrompt: true,
shell:
"items-center rounded-[10px] border border-stroke-ppg px-3.5 py-2 shadow-none sm:px-4 min-h-[44px] py-2 md:min-h-[50px]",
},
};

const monoPromptClass =
"w-full font-mono text-[13px] leading-snug text-foreground-ppg-reverse-weak sm:text-sm sm:leading-5";

export function McpBubble({
variant,
children,
}: {
variant: McpBubbleVariant;
children: ReactNode;
}) {
const { shell, fill, monoPrompt } = config[variant];

const body = monoPrompt ? <div className={monoPromptClass}>{children}</div> : children;

return (
<div className="relative w-full">
<div className={`relative z-1 flex w-full ${shell}`} style={{ backgroundColor: fill }}>
{body}
</div>
</div>
);
}
59 changes: 59 additions & 0 deletions apps/site/src/app/mcp/_components/mcp-capabilities-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { LucideIcon } from "lucide-react";

import { CapabilityCard, MobileCapabilityCard } from "./capability-cards";

export type McpCapability = {
icon: LucideIcon;
title: string;
description: string;
prompt: string;
mobileTall: boolean;
};

export function McpCapabilitiesSection({
capabilities,
}: {
capabilities: readonly McpCapability[];
}) {
return (
<section className="py-element-4xl">
<div className="mx-auto max-w-[1200px] px-4 py-12 md:px-0">
<div className="md:hidden">
<h2 className="text-center font-sans-display text-4xl font-black tracking-tight text-foreground-neutral">
What can I do with MCP?
</h2>
<div className="mx-auto mt-8 flex w-full max-w-[368px] items-center rounded-full border border-stroke-neutral bg-background-neutral-weaker p-1">
<div className="flex h-10 shrink-0 items-center rounded-full bg-background-ppg-reverse px-4 font-sans-display text-base font-bold text-foreground-ppg-reverse">
Use Prisma Postgres
</div>
<div className="flex min-w-0 flex-1 items-center justify-center px-3 font-sans-display text-base font-bold text-foreground-neutral">
Own database
</div>
</div>
<div className="mx-auto mt-8 flex max-w-[368px] flex-col gap-4">
{capabilities.map((cap) => (
<MobileCapabilityCard key={cap.title} {...cap} />
))}
</div>
</div>

<div className="hidden md:block">
<h2 className="text-center font-sans-display text-4xl font-black tracking-tight text-foreground-neutral md:text-5xl">
What can I do with MCP?
</h2>

<div className="mt-12 flex flex-col gap-4 md:flex-row md:gap-5">
{capabilities.slice(0, 2).map(({ mobileTall: _t, ...cap }) => (
<CapabilityCard key={cap.title} size="wide" {...cap} />
))}
</div>
<div className="mt-4 flex flex-col gap-4 md:flex-row md:justify-between">
{capabilities.slice(2).map(({ mobileTall: _t, ...cap }) => (
<CapabilityCard key={cap.title} size="compact" {...cap} />
))}
</div>
</div>
</div>
</section>
);
}
Loading
Loading