Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/marketing/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ pnpm-debug.log*
# jetbrains setting folder
.idea/
.vercel
.preview/
36 changes: 36 additions & 0 deletions apps/marketing/src/components/variants/copy-prompt.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
// The "Copy setup prompt" button: a dashed ghost button beside the primary,
// carrying the three agent marks. Copies `setupPrompt`; the click handler
// lives in pages/index.astro.
import { setupPrompt } from "../../content/site-copy";

const logos = import.meta.glob<string>("../../assets/logos/*.svg", {
eager: true,
query: "?url",
import: "default",
});
const logo = (name: string): string => {
const hit = Object.entries(logos).find(([k]) => k.endsWith(`/${name}.svg`));
if (!hit) throw new Error(`missing logo ${name}`);
return hit[1];
};
const marks = ["claude", "codex", "cursor"];


---

<button
type="button"
class="btn-ghost copy-btn doc__copy"
data-copy={setupPrompt}
data-event="marketing_setup_prompt_copied"
>
<span class="doc__copy-logos" aria-hidden="true">
{marks.map((n) => <img src={logo(n)} alt="" width="14" height="14" />)}
</span>
<span>Copy setup prompt</span>
<span class="copy-icons" aria-hidden="true">
<svg class="copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>
<svg class="copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg>
</span>
</button>
136 changes: 12 additions & 124 deletions apps/marketing/src/components/variants/doc-body.astro
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
---
// Document-style homepage body. Reads like a short paper about the product:
// one modest headline, plain first-person prose, six beats separated by
// hairlines, the two demos kept as numbered figures, and one way to start.
// hairlines, the two demos kept as numbered figures, and one door in: Cloud,
// with a setup prompt for visitors who already have an agent open.
// Sits inside the rail layout; the rail's "On this page" list points at the
// section ids below.
import { AnimatedBeamDemo } from "../animated-beam-demo";
import { ContextBloatDemo } from "../context-bloat-demo";
import { setupPrompt } from "../../content/site-copy";
import Testimonials from "./testimonials.astro";
import IntroSteps from "./intro-steps.astro";
import CopyPrompt from "./copy-prompt.astro";
import "../../styles/variants/doc.css";
// Avatar goes through Vite so it ships as a hashed asset; new /public files
// are not picked up by the deploy pipeline.
import rhysAvatar from "../../assets/rhys-sullivan.jpg";
// Brand marks for the hover reveals in the intro steps.
const logos = import.meta.glob<string>("../../assets/logos/*.svg", {
eager: true,
query: "?url",
import: "default",
});
const logo = (name: string): string => {
const hit = Object.entries(logos).find(([k]) => k.endsWith(`/${name}.svg`));
if (!hit) throw new Error(`missing logo ${name}`);
return hit[1];
};
const toolLogos = ["github", "linear", "sentry", "stripe", "notion", "slack", "jira", "gmail"];
const agentLogos = ["claude", "codex", "cursor", "chatgpt", "gemini", "hermes", "copilot", "windsurf", "opencode", "zed", "grok"];
// The three marks on the copy button; the full list would crowd it.
const agentLogosShort = ["claude", "codex", "cursor"];
const X_URL = "https://x.com/RhysSullivan";

interface Props {
Expand All @@ -39,91 +26,15 @@ const { gh } = Astro.props;
{/* ── Intro ── */}
<header class="doc__intro">
<h1 class="doc__title">Executor is an MCP gateway.</h1>
<ol class="doc__steps">
<li>
<span class="doc__num">01</span>
<span>
Connect{" "}
<span class="doc__pop" tabindex="0">
<span class="doc__pop-word">everything</span>
<span class="doc__pop-panel" aria-hidden="true" inert>
{toolLogos.map((n, i) => (
<span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span>
))}
<span class="doc__pop-pill" style={`--i:${toolLogos.length}`}>any OpenAPI, GraphQL, or MCP</span>
</span>
</span>
{" "}to Executor.
</span>
</li>
<li>
<span class="doc__num">02</span>
<span>
Give{" "}
<span class="doc__pop" tabindex="0">
<span class="doc__pop-word">your agent</span>
<span class="doc__pop-panel" aria-hidden="true" inert>
{agentLogos.map((n, i) => (
<span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span>
))}
<span class="doc__pop-pill" style={`--i:${agentLogos.length}`}>any MCP client</span>
</span>
</span>
{" "}the Executor tool.
</span>
</li>
<li>
<span class="doc__num">03</span>
<span>That's it.{" "}<a href="#install">Get started.</a></span>
</li>
</ol>
<p class="doc__subhead">One MCP. All your accounts.</p>
<div class="doc__tabs" role="tablist" aria-label="How to run Executor">
<button type="button" role="tab" aria-selected="true" data-doc-tab="cloud">Cloud</button>
<button type="button" role="tab" aria-selected="false" data-doc-tab="local">Desktop / CLI</button>
<button type="button" role="tab" aria-selected="false" data-doc-tab="self">Self-hosted</button>
</div>
<div class="doc__tabpanes">
<div role="tabpanel" data-doc-pane="cloud">
<p>Sign in, point your agent at one URL, done. Free for up to three people and 100,000 executions a month.</p>
<div class="doc__actions"><a href="/cloud" class="btn-primary">Start on Cloud</a><button type="button" class="btn-secondary copy-btn doc__copy" data-copy={setupPrompt} data-event="marketing_setup_prompt_copied">
<span class="doc__copy-logos" aria-hidden="true">
{agentLogosShort.map((n) => <img src={logo(n)} alt="" width="14" height="14" />)}
</span>
<span>Copy setup prompt</span>
<span class="copy-icons" aria-hidden="true">
<svg class="copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>
<svg class="copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg>
</span>
</button></div>
</div>
<div role="tabpanel" data-doc-pane="local" data-inactive aria-hidden="true" inert>
<p>Nothing leaves your machine. A desktop app for Mac, Windows, and Linux, or a CLI that runs in the background. Same local endpoint either way.</p>
<div class="doc__actions"><span class="doc__dl" data-download>
<a data-os="mac-arm64" href={`${gh}/releases/latest/download/executor-desktop-mac-arm64.dmg`} class="btn-primary">Download for Mac</a>
<a data-os="mac-x64" href={`${gh}/releases/latest/download/executor-desktop-mac-x64.dmg`} class="btn-primary">Download for Mac (Intel)</a>
<a data-os="win-x64" href={`${gh}/releases/latest/download/executor-desktop-win-x64.exe`} class="btn-primary">Download for Windows</a>
<a data-os="linux-x64" href={`${gh}/releases/latest/download/executor-desktop-linux-x86_64.AppImage`} class="btn-primary">Download for Linux</a>
<details class="download-other doc__dl-other hidden" data-download-other>
<summary>Other platforms ▾</summary>
<div class="download-other-list" data-download-other-list></div>
</details>
</span><code class="doc__cmd">npm i -g executor</code><button type="button" class="btn-secondary copy-btn doc__copy" data-copy={setupPrompt} data-event="marketing_setup_prompt_copied">
<span class="doc__copy-logos" aria-hidden="true">
{agentLogosShort.map((n) => <img src={logo(n)} alt="" width="14" height="14" />)}
</span>
<span>Copy setup prompt</span>
<span class="copy-icons" aria-hidden="true">
<svg class="copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>
<svg class="copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg>
</span>
</button></div>
</div>
<div role="tabpanel" data-doc-pane="self" data-inactive aria-hidden="true" inert>
<p>One Docker image on your own box. It is the same code Cloud runs.</p>
<div class="doc__actions"><a href="https://executor.sh/docs/hosted/docker" class="btn-primary">Self-hosting docs</a><a href={gh} class="btn-secondary">Source on GitHub</a></div>
</div>
<IntroSteps />
<p class="doc__subhead">One MCP. All your accounts. Free for up to three people.</p>
<div class="doc__actions">
<a href="/cloud" class="btn-primary">Start on Cloud</a>
<CopyPrompt />
</div>
<p class="doc__subhead doc__subhead--fine">
Rather keep it on your machine? <a href="#install">Desktop, CLI, or self-hosted ↓</a>
</p>
</header>

<figure class="doc__fig doc__fig--hero">
Expand Down Expand Up @@ -305,29 +216,6 @@ const { gh } = Astro.props;
</section>
</article>

<script>
for (const list of document.querySelectorAll<HTMLElement>(".doc__tabs")) {
const root = list.parentElement!;
list.addEventListener("click", (e) => {
const tab = (e.target as HTMLElement).closest<HTMLElement>("[data-doc-tab]");
if (!tab) return;
const key = tab.dataset.docTab;
for (const t of list.querySelectorAll<HTMLElement>("[data-doc-tab]")) {
t.setAttribute("aria-selected", String(t === tab));
}
for (const p of root.querySelectorAll<HTMLElement>("[data-doc-pane]")) {
const off = p.dataset.docPane !== key;
p.toggleAttribute("data-inactive", off);
p.toggleAttribute("inert", off);
p.setAttribute("aria-hidden", String(off));
for (const el of p.querySelectorAll<HTMLElement>("a, button")) {
el.tabIndex = off ? -1 : 0;
}
}
});
}
</script>

<script>
// Show one download button for the visitor's OS and tuck the rest behind
// "Other platforms". Without JS or on an unknown OS, all four stay visible.
Expand Down
54 changes: 54 additions & 0 deletions apps/marketing/src/components/variants/intro-steps.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
// The three numbered intro steps with the hover-reveal logo panels.
const logos = import.meta.glob<string>("../../assets/logos/*.svg", {
eager: true,
query: "?url",
import: "default",
});
const logo = (name: string): string => {
const hit = Object.entries(logos).find(([k]) => k.endsWith(`/${name}.svg`));
if (!hit) throw new Error(`missing logo ${name}`);
return hit[1];
};
const toolLogos = ["github", "linear", "sentry", "stripe", "notion", "slack", "jira", "gmail"];
const agentLogos = ["claude", "codex", "cursor", "chatgpt", "gemini", "hermes", "copilot", "windsurf", "opencode", "zed", "grok"];
---

<ol class="doc__steps">
<li>
<span class="doc__num">01</span>
<span>
Connect{" "}
<span class="doc__pop" tabindex="0">
<span class="doc__pop-word">everything</span>
<span class="doc__pop-panel" aria-hidden="true" inert>
{toolLogos.map((n, i) => (
<span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span>
))}
<span class="doc__pop-pill" style={`--i:${toolLogos.length}`}>any OpenAPI, GraphQL, or MCP</span>
</span>
</span>
{" "}to Executor.
</span>
</li>
<li>
<span class="doc__num">02</span>
<span>
Give{" "}
<span class="doc__pop" tabindex="0">
<span class="doc__pop-word">your agent</span>
<span class="doc__pop-panel" aria-hidden="true" inert>
{agentLogos.map((n, i) => (
<span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span>
))}
<span class="doc__pop-pill" style={`--i:${agentLogos.length}`}>any MCP client</span>
</span>
</span>
{" "}the Executor tool.
</span>
</li>
<li>
<span class="doc__num">03</span>
<span>That's it.</span>
</li>
</ol>
44 changes: 10 additions & 34 deletions apps/marketing/src/components/variants/rail-b.astro
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
// Rail, variant B: "Gridline rail".
// Compact and technical: mark-only wordmark, a tight link stack, an outlined
// CTA, and a numbered mono index. A full-height hairline closes the right edge.
// Compact and technical: mark-only wordmark, a tight link stack, two quiet
// meta lines (open source, YC), and a numbered mono index. The rail does not
// sell; the hero owns the call to action. A full-height hairline closes the
// right edge.
import "../../styles/variants/rail.css";
import ycBackedBy from "../../assets/yc-backed-by.svg?url";
import rhysAvatar from "../../assets/rhys-sullivan.jpg";

const X_URL = "https://x.com/RhysSullivan";
Expand Down Expand Up @@ -47,38 +48,13 @@ const pad = (index: number) => String(index + 1).padStart(2, "0");
<a href={SIGN_IN} class="rail__link rail__link--keep">Sign in</a>
</nav>

<div class="rail__cta">
<a href="#install" class="rail__box rail__box--primary">Get started</a>
<a
href={GH}
target="_blank"
rel="noopener"
class="rail__box gh-star"
aria-label="Star Executor on GitHub"
>
<svg
class="gh-star__ico"
width="13"
height="13"
viewBox="0 0 16 16"
aria-hidden="true"
>
<path
fill="#e3b341"
d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.75.75 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25z"
></path>
</svg>
<span>Star on GitHub</span>
{stars != null && <span class="gh-star__n">{stars}</span>}
<div class="rail__meta">
<a href={GH} target="_blank" rel="noopener" class="rail__meta-line">
<span>Open source</span>
{stars != null && <span class="rail__meta-n">★ {stars}</span>}
</a>
<a
href="https://www.ycombinator.com"
target="_blank"
rel="noopener noreferrer"
class="rail__box rail__box--yc"
aria-label="Backed by Y Combinator"
>
<img src={ycBackedBy} alt="Backed by Y Combinator" width="350" height="125" />
<a href="https://www.ycombinator.com" target="_blank" rel="noopener noreferrer" class="rail__meta-line">
<span>Backed by Y Combinator</span>
</a>
</div>

Expand Down
Loading
Loading