buildmymcpserver/apps/web/app/(marketing)/page.tsx
Marco Sadjadi 6f8b8da151
All checks were successful
Deploy to Production / deploy (push) Successful in 1m1s
feat(web): glow-pulse on primary CTAs + hero fills full first viewport
Two coordinated polish moves:

1. **<PulseLink> / <PulseButton>** — new `apps/web/components/pulse.tsx`.
   Click anywhere on a wrapped link or button and a small indigo dot
   detonates from the click point, scaling 1x→80x over 650ms before
   fading to transparent. Same visual language as the hero load-in
   glow — the click effectively says "this is the brand reaching back."

   The dot lives in a `pointer-events: none` overlay, so it never
   blocks the underlying navigation. `overflow-hidden + relative` are
   added to the host so the bloom stays inside the rounded shape.
   `glow-pulse` keyframe sits in globals.css next to the existing
   `pulse-dot` / `shimmer` / `fade-in` definitions; reduced-motion
   suppresses the animation to instant-opacity-0 so the click flow
   is preserved without the bloom.

   Wired into the highest-conversion CTAs only — the user explicitly
   asked "wo's Sinn macht":
   - Hero "Start building free" + "Read the docs"
   - Marketing header Login / Dashboard button
   - Dashboard header "+ New server" pill

   Deliberately NOT applied to dashboard nav links, logout, destructive
   buttons, form internals, carousel dots — pulse on every click would
   be noise.

2. **Hero fills 100svh − nav** (`min-height: calc(100svh - 3rem)`).
   `svh` (small viewport height) instead of `vh` so the hero doesn't
   jump when the mobile address bar hides/shows. The 3rem subtracts
   the sticky marketing nav (h-12 = 48px), so the hero ends right at
   the loadscreen's natural bottom edge.

   `flex items-center` plus the inner grid's existing `md:items-center`
   keep the content vertically centred inside the tall section. The
   ParticleHero background now has cinematic-scale room and the indigo
   radial-glow + dot-mask read as the dominant background motif —
   which is the effect the user loved at load-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:20:25 +02:00

372 lines
16 KiB
TypeScript

import { HeroStepRotator } from '@/components/hero-step-rotator';
import { JsonLd } from '@/components/json-ld';
import { ParticleHero } from '@/components/particle-hero';
import { PulseLink } from '@/components/pulse';
import { ScrollCue } from '@/components/scroll-cue';
import { StaticCodeBlock } from '@/components/static-code-block';
import { FAQ, faqJsonLd } from '@/lib/seo';
import Link from 'next/link';
const PROMPT_EXAMPLE = `Create an MCP server that searches our Notion workspace.
Tools: search_pages, get_page_content.
Auth: NOTION_API_KEY.`;
const OUTPUT_EXAMPLE = `> Generating spec... OK (2 tools)
> Static checks OK
> Building image bmm-mcp-notion OK 17.2s
> Deploying container OK
> Live at https://notion-x9.mcp.buildmymcpserver.com
> First request: 401 → token → 200 OK`;
const INSTALL_SNIPPET = `{
"mcpServers": {
"notion": {
"url": "https://notion-x9.mcp.buildmymcpserver.com/mcp",
"auth": "oauth2"
}
}
}`;
const EXAMPLES: { title: string; desc: string }[] = [
{ title: 'Postgres reader', desc: 'Read-only access to your tables with schema introspection.' },
{ title: 'Salesforce', desc: 'Query opportunities, accounts and leads from Claude.' },
{ title: 'Notion', desc: 'Search pages, read content, append blocks.' },
{ title: 'GitHub', desc: 'List issues, search code, post comments — scoped to one repo.' },
{ title: 'Stripe', desc: 'Look up charges, customers, refunds (read-only by default).' },
{ title: 'Custom REST', desc: 'Wrap any HTTP API behind one prompt-defined tool surface.' },
];
const MARKETPLACE_POINTS: { t: string; d: string }[] = [
{
t: 'Fork and own',
d: 'Start from a server someone already shipped. Fork it, paste your own credentials, deploy — no prompt required.',
},
{
t: 'Secrets never travel',
d: "A template carries the spec and generated code, never the author's API keys. You add your own on fork.",
},
{
t: 'Ranked by real usage',
d: 'Templates rise on fork count and active deploys — not vanity stars. The useful ones surface themselves.',
},
];
const TIERS = [
{
name: 'Hobby',
price: '€0',
tag: 'Forever free',
features: ['1 server', '100k calls/mo', 'BMM subdomain', 'Community support'],
},
{
name: 'Pro',
price: '€49',
tag: '/ month',
features: [
'5 servers',
'1M calls/mo',
'Custom domain',
'Priority build queue',
'Email support',
],
},
{
name: 'Team',
price: '€149',
tag: '/ month',
features: ['25 servers', '10M calls/mo', 'RBAC + audit log', 'SLA 99.9%', 'Slack support'],
},
{
name: 'Enterprise',
price: '€499+',
tag: '/ month',
features: ['Unlimited', 'BYOC', 'SSO / SAML', 'Dedicated cluster', 'Customer success'],
},
];
export default function Landing() {
return (
<>
{/* Hero — left: copy + CTAs, right: cycling step-rotator tile.
The old layout stacked three static code blocks vertically; the
new layout shows one centered tile that cycles through the same
three artifacts (prompt → build.log → claude config) with a
mouse-reactive 3D tilt and a step indicator. Shorter overall
so the video section below is teased above the fold. */}
<section
className="relative flex items-center overflow-hidden border-b border-[--color-border]"
style={{ minHeight: 'calc(100svh - 3rem)' }}
>
{/* WebGL particle field — capability-detected client component.
Sits behind the hero content at z-0 with pointer-events:none
so the CTAs above remain fully interactive. The canvas listens
for pointermove on window itself, so the ring still tracks
the cursor through the content above. With the hero now
filling the full first-viewport (minus the 48px sticky nav),
the field has cinematic-scale room and the indigo radial
glow + dot mask read as the dominant background motif. */}
<ParticleHero />
<div className="relative z-10 mx-auto grid w-full max-w-6xl gap-10 px-6 py-14 sm:py-20 md:grid-cols-[1.05fr_1fr] md:items-center md:gap-12">
<div className="min-w-0">
<span className="mono inline-block rounded-full border border-[--color-border] bg-[--color-bg-elevated] px-2.5 py-0.5 text-[11px] tracking-wide text-[--color-fg-muted]">
v0.1 updated 2026-05-20
</span>
<h1 className="mt-6 text-balance text-[30px] font-semibold leading-[1.06] tracking-tight sm:text-[40px] md:text-[52px]">
Describe your tool.
<br />
We host the server.
<br />
<span className="text-[--color-fg-muted]">AI uses it.</span>
</h1>
<p className="mt-5 max-w-md text-[15px] leading-relaxed text-[--color-fg-muted]">
From prompt to production MCP server in 60 seconds. OAuth 2.1, Streamable HTTP, ready
for Claude, Cursor and ChatGPT.
</p>
<div className="mt-7 flex flex-wrap items-center gap-3">
<PulseLink
href="/login"
className="inline-flex h-9 items-center justify-center rounded-md bg-[--color-accent] px-4 text-[13px] font-medium text-white transition-colors duration-200 hover:bg-[#5557e8]"
>
Start building free
</PulseLink>
<PulseLink
href="/docs"
className="inline-flex h-9 items-center justify-center rounded-md border border-[--color-border] bg-[--color-bg-elevated] px-4 text-[13px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
>
Read the docs
</PulseLink>
</div>
<div className="mt-8 flex flex-wrap gap-x-6 gap-y-2 text-[12px] text-[--color-fg-subtle]">
<span className="inline-flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-emerald-400" /> OAuth 2.1 + PKCE
</span>
<span className="inline-flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-emerald-400" /> Streamable HTTP
</span>
<span className="inline-flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-emerald-400" /> AES-256 secrets
</span>
<span className="inline-flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-emerald-400" /> Per-server isolation
</span>
</div>
</div>
<div className="relative min-w-0">
<HeroStepRotator />
</div>
</div>
</section>
{/* Scroll cue — fixed at the bottom of the loadscreen rather than
inside the hero, so it sits at the natural lower edge of the
first viewport regardless of how tall the hero ends up. Fades
out once the user has scrolled past the loadscreen. */}
<ScrollCue targetId="flow" />
{/* Flow video — full-width edge-to-edge under the hero. The clip
shows the real flow (prompt → server schematic → live connection
to Claude Desktop) in three smooth phases. autoplay-muted-loop +
playsInline satisfies every mobile browser autoplay policy; the
`poster` carries first paint while the video decodes. */}
<section
id="flow"
className="relative w-full overflow-hidden border-b border-[--color-border] bg-black"
>
<div className="relative aspect-video w-full">
<video
autoPlay
muted
loop
playsInline
preload="auto"
poster="/videos/hero-poster.jpg"
className="size-full object-cover"
aria-label="Animation: a prompt becomes a live MCP server and connects to Claude Desktop"
>
<source src="/videos/hero.webm" type="video/webm" />
<source src="/videos/hero.mp4" type="video/mp4" />
</video>
{/* Subtle vignette to integrate edges into the rest of the page */}
<div
aria-hidden
className="pointer-events-none absolute inset-0"
style={{
background:
'radial-gradient(ellipse at center, transparent 60%, rgba(10,10,11,0.55) 100%)',
}}
/>
</div>
</section>
{/* How it works */}
<section id="how" className="border-b border-[--color-border] py-14 sm:py-20">
<div className="mx-auto max-w-6xl px-6">
<div className="mb-10 max-w-2xl">
<h2 className="text-[28px] font-semibold tracking-tight">How it works</h2>
<p className="mt-2 text-[14px] text-[--color-fg-muted]">
Three steps. No JSON to write, no Docker to manage.
</p>
</div>
{/* The same video used to live here; it now has its own
full-width section directly under the hero so it's teased
above the fold and gets edge-to-edge real estate. This
section keeps the three explanatory cards as supporting
copy under the video. */}
<div className="grid gap-6 md:grid-cols-3">
{[
{
n: '01',
t: 'Describe your tool',
d: 'A sentence is enough. List your secrets and which APIs to call.',
},
{
n: '02',
t: 'We generate, check, deploy',
d: 'Claude writes the spec. We render TypeScript, run static checks, build a container, deploy to your subdomain.',
},
{
n: '03',
t: 'Install in your client',
d: 'Copy the snippet into Claude Desktop, Cursor or ChatGPT. OAuth flow on first use.',
},
].map((s) => (
<div key={s.n} className="panel p-5">
<div className="mono text-[11px] tracking-widest text-[--color-fg-subtle]">
{s.n}
</div>
<h3 className="mt-4 text-[15px] font-semibold tracking-tight">{s.t}</h3>
<p className="mt-2 text-[13px] leading-relaxed text-[--color-fg-muted]">{s.d}</p>
</div>
))}
</div>
</div>
</section>
{/* Works with */}
<section className="border-b border-[--color-border] py-12 sm:py-16">
<div className="mx-auto max-w-6xl px-6">
<h2 className="text-center text-[13px] uppercase tracking-[0.18em] text-[--color-fg-subtle]">
Works with the clients you already use
</h2>
<div className="mt-8 flex flex-wrap items-center justify-center gap-x-12 gap-y-4 text-[14px] text-[--color-fg-muted]">
{['Claude Desktop', 'Cursor', 'ChatGPT', 'VS Code Copilot', 'Continue.dev'].map((t) => (
<span key={t} className="inline-flex items-center gap-2">
<span className="size-1.5 rounded-full bg-[--color-fg-subtle]" />
{t}
</span>
))}
</div>
</div>
</section>
{/* Examples */}
<section className="border-b border-[--color-border] py-14 sm:py-20">
<div className="mx-auto max-w-6xl px-6">
<div className="mb-10 max-w-2xl">
<h2 className="text-[28px] font-semibold tracking-tight">
Built for the work you actually have
</h2>
<p className="mt-2 text-[14px] text-[--color-fg-muted]">
Anything with an HTTP API or a database, in minutes.
</p>
</div>
<div className="grid gap-3 md:grid-cols-3">
{EXAMPLES.map((e) => (
<div
key={e.title}
className="panel p-4 transition-colors hover:border-[--color-border-strong]"
>
<div className="text-[13px] font-semibold tracking-tight">{e.title}</div>
<p className="mt-1 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
{e.desc}
</p>
</div>
))}
</div>
</div>
</section>
{/* Marketplace */}
<section className="border-b border-[--color-border] py-14 sm:py-20">
<div className="mx-auto max-w-6xl px-6">
<div className="mb-10 flex flex-wrap items-end justify-between gap-4">
<div className="max-w-2xl">
<h2 className="text-[28px] font-semibold tracking-tight">
Start from a template, ship in seconds
</h2>
<p className="mt-2 text-[14px] text-[--color-fg-muted]">
The marketplace is a library of working MCP servers the community already built.
Fork one to skip the prompt or publish your own and let others build on it.
</p>
</div>
<Link
href="/templates"
className="inline-flex h-9 shrink-0 items-center justify-center rounded-md border border-[--color-border] bg-[--color-bg-elevated] px-4 text-[13px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
>
Browse the marketplace
</Link>
</div>
<div className="grid gap-3 md:grid-cols-3">
{MARKETPLACE_POINTS.map((p) => (
<div key={p.t} className="panel p-5">
<h3 className="text-[15px] font-semibold tracking-tight">{p.t}</h3>
<p className="mt-2 text-[13px] leading-relaxed text-[--color-fg-muted]">{p.d}</p>
</div>
))}
</div>
</div>
</section>
{/* Pricing */}
<section id="pricing" className="border-b border-[--color-border] py-14 sm:py-20">
<div className="mx-auto max-w-6xl px-6">
<div className="mb-10 max-w-2xl">
<h2 className="text-[28px] font-semibold tracking-tight">Pricing</h2>
<p className="mt-2 text-[14px] text-[--color-fg-muted]">
Pay for tool calls, not for boilerplate.
</p>
</div>
<div className="grid gap-3 md:grid-cols-4">
{TIERS.map((t, i) => (
<div
key={t.name}
className={`panel p-5 ${i === 1 ? 'border-[--color-accent]/40' : ''}`}
>
<div className="text-[12px] uppercase tracking-wider text-[--color-fg-subtle]">
{t.name}
</div>
<div className="mt-2 flex items-baseline gap-1">
<span className="text-[26px] font-semibold tracking-tight">{t.price}</span>
<span className="text-[12px] text-[--color-fg-subtle]">{t.tag}</span>
</div>
<ul className="mt-4 space-y-1.5 text-[12.5px] text-[--color-fg-muted]">
{t.features.map((f) => (
<li key={f}> {f}</li>
))}
</ul>
</div>
))}
</div>
</div>
</section>
{/* FAQ */}
<section className="py-14 sm:py-20">
<JsonLd data={faqJsonLd()} />
<div className="mx-auto max-w-6xl px-6">
<h2 className="text-[28px] font-semibold tracking-tight">FAQ</h2>
<div className="mt-8 grid gap-x-12 gap-y-6 md:grid-cols-2">
{FAQ.map((f) => (
<div key={f.q}>
<h3 className="text-[14px] font-semibold tracking-tight">{f.q}</h3>
<p className="mt-1.5 text-[13px] leading-relaxed text-[--color-fg-muted]">{f.a}</p>
</div>
))}
</div>
</div>
</section>
</>
);
}