History

Merge pull request #695 from RhysSullivan/marketing-rework

Rhys Sullivan committed 6 months ago

Commit 92279f22 parents d77a16d 7fe563212 files · +940 −1264Browse files

Unified · Split

12 files
  1. apps/marketing/astro.config.mjs +2 −0
  2. apps/marketing/package.json +8 −0
  3. apps/marketing/public/pattern-graph-paper.svg +1 −0
  4. apps/marketing/src/components/LegalLayout.astro +0 −3
  5. apps/marketing/src/components/animated-beam-demo.tsx +371 −0
  6. apps/marketing/src/components/ui/animated-beam.tsx +167 −0
  7. apps/marketing/src/layouts/Layout.astro +5 −37
  8. apps/marketing/src/lib/utils.ts +6 −0
  9. apps/marketing/src/pages/index.astro +236 −914
  10. apps/marketing/src/pages/setup.astro +0 −172
  11. apps/marketing/src/styles/global.css +109 −138
  12. bun.lock +35 −0
apps/marketing/astro.config.mjs+2−0
@@ -2,12 +2,14 @@ · 1 unmodified line
22 import { defineConfig } from "astro/config";
33
44 import tailwindcss from "@tailwindcss/vite";
5+import react from "@astrojs/react";
56
67 import cloudflare from "@astrojs/cloudflare";
78
89 // https://astro.build/config
910 export default defineConfig({
1011 output: "server",
12+ integrations: [react()],
1113 vite: {
1214 plugins: [tailwindcss()],
1315 },
apps/marketing/package.json+8−0
@@ -15,18 +15,26 @@ · 14 unmodified lines
1515 },
1616 "dependencies": {
1717 "@astrojs/cloudflare": "^13.0.0",
18+ "@astrojs/react": "^5.0.4",
1819 "@executor-js/plugin-google-discovery": "workspace:*",
1920 "@executor-js/plugin-graphql": "workspace:*",
2021 "@executor-js/plugin-openapi": "workspace:*",
2122 "@executor-js/sdk": "workspace:*",
2223 "@tailwindcss/vite": "^4.2.2",
2324 "astro": "^6.1.3",
25+ "clsx": "^2.1.1",
2426 "effect": "catalog:",
27+ "motion": "^12.38.0",
2528 "posthog-js": "^1.372.5",
29+ "react": "^19.2.5",
30+ "react-dom": "^19.2.5",
31+ "tailwind-merge": "^3.5.0",
2632 "tailwindcss": "^4.2.2"
2733 },
2834 "devDependencies": {
2935 "@rhyssul/portless": "^0.13.0",
36+ "@types/react": "^19.2.14",
37+ "@types/react-dom": "^19.2.3",
3038 "wrangler": "^4.0.0"
3139 },
3240 "engines": {
apps/marketing/public/pattern-graph-paper.svg+1−0
@@ -0,0 +1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill-rule="evenodd"><g fill="#000"><path opacity=".5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z"/><path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z"/></g></g></svg>
apps/marketing/src/components/LegalLayout.astro+0−3
@@ -17,9 +17,6 @@ const { title, description, lastUpdated } = Astro.props; · 16 unmodified lines
1717 executor
1818 </a>
1919 <div class="flex items-center gap-5 sm:gap-8">
20− <a href="/setup" class="hidden text-[13px] text-ink-muted transition-colors hover:text-ink sm:block">
21− Setup
22− </a>
2320 <a href="/privacy" class="text-[13px] text-ink-muted transition-colors hover:text-ink">
2421 Privacy
2522 </a>
apps/marketing/src/components/animated-beam-demo.tsx+371−0
@@ -0,0 +1,371 @@
1+"use client";
2+
3+import React, { forwardRef, useRef } from "react";
4+
5+import { cn } from "../lib/utils";
6+import { AnimatedBeam } from "./ui/animated-beam";
7+
8+type Variant = "blueprint" | "brutalist" | "pastel" | "cyber" | "editorial" | "stripe";
9+
10+const variantStyles: Record<
11+ Variant,
12+ {
13+ nodeBg: string;
14+ nodeBorder: string;
15+ nodeRadius: string;
16+ hubBg: string;
17+ hubBorder: string;
18+ label: string;
19+ sub: string;
20+ beam: string;
21+ path: string;
22+ pathOpacity: number;
23+ pathWidth: number;
24+ }
25+> = {
26+ blueprint: {
27+ nodeBg: "#f6f4ec",
28+ nodeBorder: "rgba(10,10,10,0.18)",
29+ nodeRadius: "6px",
30+ hubBg: "#f6f4ec",
31+ hubBorder: "#1a3aff",
32+ label: "#0a0a0a",
33+ sub: "#8a8a82",
34+ beam: "#1a3aff",
35+ path: "rgba(10,10,10,0.18)",
36+ pathOpacity: 0.3,
37+ pathWidth: 1,
38+ },
39+ brutalist: {
40+ nodeBg: "#ffffff",
41+ nodeBorder: "#000000",
42+ nodeRadius: "0px",
43+ hubBg: "#000000",
44+ hubBorder: "#000000",
45+ label: "#000000",
46+ sub: "#000000",
47+ beam: "#f0ff00",
48+ path: "rgba(0,0,0,0.5)",
49+ pathOpacity: 1,
50+ pathWidth: 2,
51+ },
52+ pastel: {
53+ nodeBg: "#ffffff",
54+ nodeBorder: "rgba(42,32,28,0.12)",
55+ nodeRadius: "12px",
56+ hubBg: "#ffffff",
57+ hubBorder: "#c45a3a",
58+ label: "#2a201c",
59+ sub: "#6b5b53",
60+ beam: "#c45a3a",
61+ path: "rgba(42,32,28,0.15)",
62+ pathOpacity: 0.6,
63+ pathWidth: 1.5,
64+ },
65+ cyber: {
66+ nodeBg: "#0e0e1a",
67+ nodeBorder: "rgba(255,255,255,0.18)",
68+ nodeRadius: "4px",
69+ hubBg: "#0e0e1a",
70+ hubBorder: "#ff2a86",
71+ label: "#f0f0f5",
72+ sub: "#6c6c85",
73+ beam: "#00f0ff",
74+ path: "rgba(255,255,255,0.18)",
75+ pathOpacity: 0.5,
76+ pathWidth: 1,
77+ },
78+ editorial: {
79+ nodeBg: "#f4ede0",
80+ nodeBorder: "rgba(42,31,21,0.28)",
81+ nodeRadius: "9999px",
82+ hubBg: "#f4ede0",
83+ hubBorder: "#a14628",
84+ label: "#2a1f15",
85+ sub: "#a89884",
86+ beam: "#a14628",
87+ path: "rgba(42,31,21,0.22)",
88+ pathOpacity: 0.5,
89+ pathWidth: 1,
90+ },
91+ stripe: {
92+ nodeBg: "#ffffff",
93+ nodeBorder: "rgba(10,37,64,0.10)",
94+ nodeRadius: "10px",
95+ hubBg: "#ffffff",
96+ hubBorder: "#635bff",
97+ label: "#0a2540",
98+ sub: "#8898a4",
99+ beam: "#635bff",
100+ path: "rgba(10,37,64,0.10)",
101+ pathOpacity: 0.4,
102+ pathWidth: 1.25,
103+ },
104+};
105+
106+const Node = forwardRef<
107+ HTMLDivElement,
108+ {
109+ className?: string;
110+ children?: React.ReactNode;
111+ size?: "sm" | "lg";
112+ style?: React.CSSProperties;
113+ }
114+>(({ className, children, size = "sm", style }, ref) => {
115+ return (
116+ <div
117+ ref={ref}
118+ style={style}
119+ className={cn(
120+ "z-10 flex items-center justify-center",
121+ size === "sm" ? "size-10 p-2" : "size-16 p-2",
122+ className,
123+ )}
124+ >
125+ {children}
126+ </div>
127+ );
128+});
129+Node.displayName = "Node";
130+
131+export function AnimatedBeamDemo({
132+ className,
133+ variant = "blueprint",
134+}: {
135+ className?: string;
136+ variant?: Variant;
137+}) {
138+ const containerRef = useRef<HTMLDivElement>(null);
139+ const agent1 = useRef<HTMLDivElement>(null);
140+ const agent2 = useRef<HTMLDivElement>(null);
141+ const agent3 = useRef<HTMLDivElement>(null);
142+ const hub = useRef<HTMLDivElement>(null);
143+ const tool1 = useRef<HTMLDivElement>(null);
144+ const tool2 = useRef<HTMLDivElement>(null);
145+ const tool3 = useRef<HTMLDivElement>(null);
146+
147+ const v = variantStyles[variant];
148+ const beamColor = v.beam;
149+ const pathColor = v.path;
150+ const pathOpacity = v.pathOpacity;
151+ const pathWidth = v.pathWidth;
152+ const nodeStyle: React.CSSProperties = {
153+ background: v.nodeBg,
154+ border: `1px solid ${v.nodeBorder}`,
155+ borderRadius: v.nodeRadius,
156+ };
157+ const hubStyle: React.CSSProperties = {
158+ background: v.hubBg,
159+ border: `${variant === "brutalist" ? "2px" : "1px"} solid ${v.hubBorder}`,
160+ borderRadius: v.nodeRadius,
161+ };
162+ const labelStyle: React.CSSProperties = { color: v.label };
163+ const subStyle: React.CSSProperties = { color: v.sub };
164+
165+ return (
166+ <div
167+ ref={containerRef}
168+ className={cn("relative flex w-full items-center justify-center py-2", className)}
169+ >
170+ <div className="flex w-full flex-row items-stretch justify-between gap-6 sm:gap-8">
171+ {/* Agents (left) */}
172+ <div className="flex flex-col justify-center gap-7">
173+ <Row label="Claude Code" reverse={false} labelStyle={labelStyle}>
174+ <Node ref={agent1} style={nodeStyle}>
175+ <Icons.claude />
176+ </Node>
177+ </Row>
178+ <Row label="Cursor" reverse={false} labelStyle={labelStyle}>
179+ <Node ref={agent2} style={nodeStyle}>
180+ <Icons.cursor />
181+ </Node>
182+ </Row>
183+ <Row label="Codex" reverse={false} labelStyle={labelStyle}>
184+ <Node ref={agent3} style={nodeStyle}>
185+ <Icons.openai />
186+ </Node>
187+ </Row>
188+ </div>
189+
190+ {/* Hub (center) */}
191+ <div className="flex flex-col justify-center">
192+ <Node ref={hub} size="lg" style={hubStyle}>
193+ <img
194+ src="/favicon-192.png"
195+ alt="Executor"
196+ className="w-full h-full object-contain"
197+ style={
198+ variant === "cyber" || variant === "brutalist" ? { filter: "invert(1)" } : undefined
199+ }
200+ />
201+ </Node>
202+ </div>
203+
204+ {/* Tools (right) */}
205+ <div className="flex flex-col justify-center gap-7">
206+ <Row label="Sentry" sub="OpenAPI" reverse labelStyle={labelStyle} subStyle={subStyle}>
207+ <Node ref={tool1} style={nodeStyle}>
208+ <Icons.sentry />
209+ </Node>
210+ </Row>
211+ <Row label="GitHub" sub="GraphQL" reverse labelStyle={labelStyle} subStyle={subStyle}>
212+ <Node ref={tool2} style={nodeStyle}>
213+ <Icons.github />
214+ </Node>
215+ </Row>
216+ <Row label="Linear" sub="MCP" reverse labelStyle={labelStyle} subStyle={subStyle}>
217+ <Node ref={tool3} style={nodeStyle}>
218+ <Icons.linear />
219+ </Node>
220+ </Row>
221+ </div>
222+ </div>
223+
224+ {/* Beams: agents → hub */}
225+ <AnimatedBeam
226+ containerRef={containerRef}
227+ fromRef={agent1}
228+ toRef={hub}
229+ curvature={-50}
230+ pathColor={pathColor}
231+ pathOpacity={pathOpacity}
232+ pathWidth={pathWidth}
233+ gradientStartColor={beamColor}
234+ gradientStopColor={beamColor}
235+ duration={4}
236+ />
237+ <AnimatedBeam
238+ containerRef={containerRef}
239+ fromRef={agent2}
240+ toRef={hub}
241+ curvature={0}
242+ pathColor={pathColor}
243+ pathOpacity={pathOpacity}
244+ pathWidth={pathWidth}
245+ gradientStartColor={beamColor}
246+ gradientStopColor={beamColor}
247+ duration={4}
248+ delay={0.3}
249+ />
250+ <AnimatedBeam
251+ containerRef={containerRef}
252+ fromRef={agent3}
253+ toRef={hub}
254+ curvature={50}
255+ pathColor={pathColor}
256+ pathOpacity={pathOpacity}
257+ pathWidth={pathWidth}
258+ gradientStartColor={beamColor}
259+ gradientStopColor={beamColor}
260+ duration={4}
261+ delay={0.6}
262+ />
263+
264+ {/* Beams: hub → tools */}
265+ <AnimatedBeam
266+ containerRef={containerRef}
267+ fromRef={hub}
268+ toRef={tool1}
269+ curvature={50}
270+ pathColor={pathColor}
271+ pathOpacity={pathOpacity}
272+ pathWidth={pathWidth}
273+ gradientStartColor={beamColor}
274+ gradientStopColor={beamColor}
275+ duration={4}
276+ delay={0.15}
277+ />
278+ <AnimatedBeam
279+ containerRef={containerRef}
280+ fromRef={hub}
281+ toRef={tool2}
282+ curvature={0}
283+ pathColor={pathColor}
284+ pathOpacity={pathOpacity}
285+ pathWidth={pathWidth}
286+ gradientStartColor={beamColor}
287+ gradientStopColor={beamColor}
288+ duration={4}
289+ delay={0.45}
290+ />
291+ <AnimatedBeam
292+ containerRef={containerRef}
293+ fromRef={hub}
294+ toRef={tool3}
295+ curvature={-50}
296+ pathColor={pathColor}
297+ pathOpacity={pathOpacity}
298+ pathWidth={pathWidth}
299+ gradientStartColor={beamColor}
300+ gradientStopColor={beamColor}
301+ duration={4}
302+ delay={0.75}
303+ />
304+ </div>
305+ );
306+}
307+
308+function Row({
309+ children,
310+ label,
311+ sub,
312+ reverse,
313+ labelStyle,
314+ subStyle,
315+}: {
316+ children: React.ReactNode;
317+ label: string;
318+ sub?: string;
319+ reverse: boolean;
320+ labelStyle?: React.CSSProperties;
321+ subStyle?: React.CSSProperties;
322+}) {
323+ return (
324+ <div className={cn("flex items-center gap-3", reverse && "flex-row-reverse text-right")}>
325+ {children}
326+ <div className="flex flex-col leading-tight">
327+ <span className="text-[12px] font-medium" style={labelStyle}>
328+ {label}
329+ </span>
330+ {sub ? (
331+ <span className="font-mono text-[10px] tracking-tight" style={subStyle}>
332+ {sub}
333+ </span>
334+ ) : null}
335+ </div>
336+ </div>
337+ );
338+}
339+
340+const Icons = {
341+ sentry: () => (
342+ <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#362D59]">
343+ <path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z" />
344+ </svg>
345+ ),
346+ github: () => (
347+ <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]">
348+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
349+ </svg>
350+ ),
351+ linear: () => (
352+ <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#5E6AD2]">
353+ <path d="M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z" />
354+ </svg>
355+ ),
356+ claude: () => (
357+ <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#D97757]">
358+ <path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" />
359+ </svg>
360+ ),
361+ cursor: () => (
362+ <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]">
363+ <path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" />
364+ </svg>
365+ ),
366+ openai: () => (
367+ <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]">
368+ <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
369+ </svg>
370+ ),
371+};
apps/marketing/src/components/ui/animated-beam.tsx+167−0
@@ -0,0 +1,167 @@
1+"use client";
2+
3+import { useEffect, useId, useState, type RefObject } from "react";
4+import { motion } from "motion/react";
5+
6+import { cn } from "../../lib/utils";
7+
8+export interface AnimatedBeamProps {
9+ className?: string;
10+ containerRef: RefObject<HTMLElement | null>; // Container ref
11+ fromRef: RefObject<HTMLElement | null>;
12+ toRef: RefObject<HTMLElement | null>;
13+ curvature?: number;
14+ reverse?: boolean;
15+ pathColor?: string;
16+ pathWidth?: number;
17+ pathOpacity?: number;
18+ gradientStartColor?: string;
19+ gradientStopColor?: string;
20+ delay?: number;
21+ duration?: number;
22+ repeat?: number;
23+ repeatDelay?: number;
24+ startXOffset?: number;
25+ startYOffset?: number;
26+ endXOffset?: number;
27+ endYOffset?: number;
28+}
29+
30+export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
31+ className,
32+ containerRef,
33+ fromRef,
34+ toRef,
35+ curvature = 0,
36+ reverse = false, // Include the reverse prop
37+ duration = 5,
38+ delay = 0,
39+ pathColor = "gray",
40+ pathWidth = 2,
41+ pathOpacity = 0.2,
42+ gradientStartColor = "#ffaa40",
43+ gradientStopColor = "#9c40ff",
44+ repeat = Infinity,
45+ repeatDelay = 0,
46+ startXOffset = 0,
47+ startYOffset = 0,
48+ endXOffset = 0,
49+ endYOffset = 0,
50+}) => {
51+ const id = useId();
52+ const [pathD, setPathD] = useState("");
53+ const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
54+
55+ // Calculate the gradient coordinates based on the reverse prop
56+ const gradientCoordinates = reverse
57+ ? {
58+ x1: ["90%", "-10%"],
59+ x2: ["100%", "0%"],
60+ y1: ["0%", "0%"],
61+ y2: ["0%", "0%"],
62+ }
63+ : {
64+ x1: ["10%", "110%"],
65+ x2: ["0%", "100%"],
66+ y1: ["0%", "0%"],
67+ y2: ["0%", "0%"],
68+ };
69+
70+ useEffect(() => {
71+ const updatePath = () => {
72+ if (containerRef.current && fromRef.current && toRef.current) {
73+ const containerRect = containerRef.current.getBoundingClientRect();
74+ const rectA = fromRef.current.getBoundingClientRect();
75+ const rectB = toRef.current.getBoundingClientRect();
76+
77+ const svgWidth = containerRect.width;
78+ const svgHeight = containerRect.height;
79+ setSvgDimensions({ width: svgWidth, height: svgHeight });
80+
81+ const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
82+ const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
83+ const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
84+ const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
85+
86+ const controlY = startY - curvature;
87+ const d = `M ${startX},${startY} Q ${(startX + endX) / 2},${controlY} ${endX},${endY}`;
88+ setPathD(d);
89+ }
90+ };
91+
92+ // Initialize ResizeObserver
93+ const resizeObserver = new ResizeObserver(() => {
94+ updatePath();
95+ });
96+
97+ // Observe the container element
98+ if (containerRef.current) {
99+ resizeObserver.observe(containerRef.current);
100+ }
101+
102+ // Call the updatePath initially to set the initial path
103+ updatePath();
104+
105+ // Clean up the observer on component unmount
106+ return () => {
107+ resizeObserver.disconnect();
108+ };
109+ }, [containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset]);
110+
111+ return (
112+ <svg
113+ fill="none"
114+ width={svgDimensions.width}
115+ height={svgDimensions.height}
116+ xmlns="http://www.w3.org/2000/svg"
117+ className={cn("pointer-events-none absolute top-0 left-0 transform-gpu stroke-2", className)}
118+ viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
119+ >
120+ <path
121+ d={pathD}
122+ stroke={pathColor}
123+ strokeWidth={pathWidth}
124+ strokeOpacity={pathOpacity}
125+ strokeLinecap="round"
126+ />
127+ <path
128+ d={pathD}
129+ strokeWidth={pathWidth}
130+ stroke={`url(#${id})`}
131+ strokeOpacity="1"
132+ strokeLinecap="round"
133+ />
134+ <defs>
135+ <motion.linearGradient
136+ className="transform-gpu"
137+ id={id}
138+ gradientUnits={"userSpaceOnUse"}
139+ initial={{
140+ x1: "0%",
141+ x2: "0%",
142+ y1: "0%",
143+ y2: "0%",
144+ }}
145+ animate={{
146+ x1: gradientCoordinates.x1,
147+ x2: gradientCoordinates.x2,
148+ y1: gradientCoordinates.y1,
149+ y2: gradientCoordinates.y2,
150+ }}
151+ transition={{
152+ delay,
153+ duration,
154+ ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
155+ repeat,
156+ repeatDelay,
157+ }}
158+ >
159+ <stop stopColor={gradientStartColor} stopOpacity="0"></stop>
160+ <stop stopColor={gradientStartColor}></stop>
161+ <stop offset="32.5%" stopColor={gradientStopColor}></stop>
162+ <stop offset="100%" stopColor={gradientStopColor} stopOpacity="0"></stop>
163+ </motion.linearGradient>
164+ </defs>
165+ </svg>
166+ );
167+};
apps/marketing/src/layouts/Layout.astro+5−37
@@ -7,8 +7,8 @@ interface Props { · 6 unmodified lines
77 }
88
99 const {
10− title = 'Executor — The Integration Layer for AI Agents',
11− description = 'A unified execution environment that gives AI agents typed, sandboxed access to every tool and API. OpenAPI, GraphQL, MCP, and more — one catalog, zero CLI sprawl.'
10+ title = 'Executor — The gateway to connect your agent to everything',
11+ description = 'One place every agent plugs into every tool you already use. Wire it up once, bring the whole team.'
1212 } = Astro.props
1313 ---
1414
@@ -20,48 +20,16 @@ const { · 5 unmodified lines
2020 <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" />
2121 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
2222 <meta name="viewport" content="width=device-width, initial-scale=1" />
23− <meta name="color-scheme" content="light dark" />
23+ <meta name="color-scheme" content="light" />
2424 <meta name="description" content={description} />
2525 <meta property="og:title" content={title} />
2626 <meta property="og:description" content={description} />
2727 <title>{title}</title>
2828 <link rel="preconnect" href="https://fonts.googleapis.com" />
2929 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
30− <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
30+ <link href="https://fonts.googleapis.com/css2?family=Anton&family=Fraunces:ital,opsz,wght@0,9..144,400..900;1,9..144,400..900&family=Geist:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
3131 </head>
32− <body class="bg-surface text-ink antialiased selection:bg-teal/20 selection:text-teal">
32+ <body class="antialiased">
3333 <slot />
34−
35− <script>
36− import posthog from 'posthog-js'
37−
38− const key = import.meta.env.PUBLIC_POSTHOG_KEY
39− if (key) {
40− const analyticsPath = (import.meta.env.PUBLIC_ANALYTICS_PATH ?? 'a').replace(/^\/+|\/+$/g, '')
41− posthog.init(key, {
42− api_host:
43− import.meta.env.PUBLIC_POSTHOG_HOST ?? `${window.location.origin}/api/${analyticsPath}`,
44− ui_host: 'https://us.posthog.com',
45− defaults: '2025-05-24',
46− person_profiles: 'identified_only',
47− })
48− }
49− </script>
50−
51− <script>
52− // Scroll-triggered reveals
53− const observer = new IntersectionObserver(
54− (entries) => {
55− entries.forEach((entry) => {
56− if (entry.isIntersecting) {
57− entry.target.classList.add('visible');
58− observer.unobserve(entry.target);
59− }
60− });
61− },
62− { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }
63− );
64− document.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
65− </script>
6634 </body>
6735 </html>
apps/marketing/src/lib/utils.ts+6−0
@@ -0,0 +1,6 @@
1+import { type ClassValue, clsx } from "clsx";
2+import { twMerge } from "tailwind-merge";
3+
4+export function cn(...inputs: ClassValue[]) {
5+ return twMerge(clsx(inputs));
6+}
apps/marketing/src/pages/index.astro+236−914
@@ -1,971 +1,293 @@
11 ---
2−export const prerender = true
32 import Layout from '../layouts/Layout.astro'
3+import { AnimatedBeamDemo } from '../components/animated-beam-demo'
44 ---
55
66 <Layout>
7− <!-- ─── NAV ─── -->
8− <nav class="fixed top-0 w-full z-50 bg-surface/80 backdrop-blur-lg">
9− <div class="max-w-[1200px] mx-auto px-6 sm:px-10 h-14 flex items-center justify-between">
10− <a href="/" class="font-display text-xl italic text-ink tracking-tight hover:text-teal transition-colors">executor</a>
11− <div class="flex items-center gap-6 sm:gap-8">
12− <a href="#roadmap" class="hidden sm:block text-[13px] text-ink-muted hover:text-ink transition-colors">Roadmap</a>
13− <a href="https://github.com/RhysSullivan/executor" class="hidden sm:block text-[13px] text-ink-muted hover:text-ink transition-colors">GitHub</a>
14− <a href="/cloud" class="text-[13px] font-medium text-surface bg-teal hover:bg-teal-dim px-4 py-1.5 rounded-md transition-colors">
15− Try Cloud
16− </a>
17− </div>
18− </div>
19− </nav>
7+ <main class="bg-soft min-h-dvh relative antialiased">
208
21− <!-- ─── HERO ─── -->
22− <section class="relative min-h-screen flex flex-col justify-center pt-14 overflow-hidden">
23− <div class="absolute top-[15%] right-[10%] w-[500px] h-[500px] bg-teal/[0.04] rounded-full blur-[150px] pointer-events-none"></div>
9+ {/* Pattern + fade — span the top 100vh, behind everything */}
10+ <div class="pattern-bg pattern-graph" style="position: absolute; top: 0; left: 0; right: 0; height: 100vh; z-index: 1; pointer-events: none;"></div>
11+ <div style="position: absolute; top: 0; left: 0; right: 0; height: 100vh; z-index: 2; pointer-events: none; background: linear-gradient(180deg, transparent 0%, transparent 65%, var(--color-surface) 100%);"></div>
2412
25− <div class="max-w-[1200px] mx-auto px-6 sm:px-10 w-full">
26− <!-- Text centered -->
27− <div class="text-center max-w-2xl mx-auto mt-10 mb-16">
28− <h1 class="rise-d1 font-display text-[clamp(3rem,7vw,5.5rem)] leading-[0.95] tracking-tight mb-8">
29− <span class="text-ink">Your missing<br /></span><span class="italic text-teal">integration&nbsp;layer</span>
30− </h1>
13+ <div class="relative z-10">
3114
32− <p class="rise-d2 text-[17px] leading-[1.7] text-ink-muted mb-10">
33− One catalog. Every agent. Shared auth, policies, and tools across
34− Cursor, Claude Code, OpenCode&mdash;or your own.
35− </p>
36−
37− <div class="rise-d3 flex flex-wrap items-center justify-center gap-3">
38− <a href="/cloud" class="inline-flex items-center gap-2 text-[14px] font-medium text-surface bg-teal hover:bg-teal-dim pl-5 pr-3 py-2.5 rounded-md transition-colors">
39− Try Cloud
40− <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>
41− </a>
42− <a href="#get-started" class="inline-flex items-center gap-2 text-[14px] font-medium text-ink-muted hover:text-ink border border-rule hover:border-ink-faint px-5 py-2.5 rounded-md transition-colors">
43− Run it locally
44− </a>
15+ {/* NAV */}
16+ <nav>
17+ <div class="max-w-[1200px] mx-auto px-6 sm:px-10 h-14 flex items-center justify-between">
18+ <a href="/" class="flex items-center gap-2.5">
19+ <img src="/favicon-192.png" alt="" class="w-5 h-5" />
20+ <span class="text-[15px] font-semibold tracking-[-0.01em] text-ink">executor</span>
21+ </a>
22+ <div class="flex items-center gap-5">
23+ <a href="https://github.com/RhysSullivan/executor" class="text-[14px] font-medium text-ink-2 hover:text-ink transition-colors">GitHub</a>
24+ <a href="/cloud" class="btn-primary">Try Cloud →</a>
4525 </div>
4626 </div>
47−
48− <!-- URL input bar -->
49− <div class="rise-d4 mb-4" id="detect-bar">
50− <div class="relative">
51− <div class="flex items-center gap-3 bg-surface-raised border border-rule rounded-xl px-5 py-3.5 focus-within:border-teal/30 transition-colors">
52− <svg class="w-4 h-4 text-ink-faint shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.414-5.874a4.5 4.5 0 00-6.364 0l-4.5 4.5a4.5 4.5 0 001.242 7.244" /></svg>
53− <input
54− id="detect-input"
55− type="url"
56− placeholder="Paste any API URL to detect tools..."
57− class="flex-1 bg-transparent text-[14px] text-ink placeholder:text-ink-faint outline-none font-mono"
58− autocomplete="off"
59− spellcheck="false"
60− />
61− <button id="detect-btn" class="text-[12px] font-medium text-surface bg-teal hover:bg-teal-dim px-4 py-1.5 rounded-md transition-colors shrink-0 disabled:opacity-40 disabled:cursor-not-allowed">
62− Detect
63− </button>
27+ </nav>
28+
29+ {/* HERO — centered, tight */}
30+ <section class="relative">
31+ <div class="relative z-10 px-6 sm:px-10 pt-28 pb-32 sm:pt-36 sm:pb-44">
32+ <div class="max-w-[920px] mx-auto text-center">
33+ <h1 class="rise-1 text-[clamp(2.8rem,7.2vw,6rem)] font-semibold tracking-[-0.025em] leading-[1.02] text-ink mb-12 mx-auto max-w-[18ch] text-balance">
34+ Connect any agent to <span class="serif-italic">everything</span>.
35+ </h1>
36+ <div class="rise-2 max-w-[760px] mx-auto mb-12">
37+ <div class="surface-card p-6 sm:p-7">
38+ <AnimatedBeamDemo client:load variant="stripe" />
39+ </div>
6440 </div>
65− <div id="detect-status" class="hidden absolute left-0 right-0 -bottom-7 text-[11px] font-mono text-center"></div>
66− <div id="detect-examples" class="mt-3 flex flex-wrap items-center gap-2 justify-center">
67− <span class="text-[11px] text-ink-faint">Try:</span>
68− <button class="detect-example text-[11px] font-mono text-teal/60 hover:text-teal border border-rule hover:border-teal/20 rounded px-2 py-1 transition-colors" data-url="https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json">GitHub REST</button>
69− <button class="detect-example text-[11px] font-mono text-teal/60 hover:text-teal border border-rule hover:border-teal/20 rounded px-2 py-1 transition-colors" data-url="https://api.linear.app/graphql">Linear GraphQL</button>
70− <button class="detect-example text-[11px] font-mono text-teal/60 hover:text-teal border border-rule hover:border-teal/20 rounded px-2 py-1 transition-colors" data-url="https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest">Google Calendar</button>
71− <button class="detect-example text-[11px] font-mono text-teal/60 hover:text-teal border border-rule hover:border-teal/20 rounded px-2 py-1 transition-colors" data-url="https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json">Stripe</button>
41+ <p class="rise-3 text-[18px] leading-[1.55] text-ink-2 mx-auto max-w-[44ch] mb-9 text-pretty">
42+ One gateway. Every tool. Every team.
43+ </p>
44+ <div class="rise-4 flex flex-wrap items-center justify-center gap-5">
45+ <a href="/cloud" class="btn-primary">Try Cloud →</a>
46+ <a href="#install" class="btn-link">
47+ <span class="font-mono text-[13px] text-ink-3">$</span>
48+ <span>npx executor web</span>
49+ </a>
7250 </div>
7351 </div>
7452 </div>
53+ </section>
54+
55+ {/* ─── PILLARS ─── */}
56+ <section class="py-24 sm:py-32">
57+ <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
58+ <div class="mb-16 max-w-2xl">
59+ <div class="font-mono text-[12px] uppercase tracking-[0.18em] text-ink-3 mb-4">What it does</div>
60+ <h2 class="text-[clamp(2.2rem,4.5vw,3.4rem)] font-semibold tracking-[-0.02em] leading-[1.05] text-ink mb-5 max-w-[24ch] text-balance">
61+ Connect once. <span class="serif-italic">Use everywhere</span>.
62+ </h2>
63+ <p class="text-[17px] leading-[1.55] text-ink-2 max-w-[56ch] text-pretty">
64+ One layer between your agents and the tools they need. Bring any spec, plug in any agent, govern every action.
65+ </p>
66+ </div>
7567
76− <!-- Full-width interactive catalog UI -->
77− <div class="rise-d4 relative" id="hero-catalog">
78− <div class="absolute -inset-px rounded-xl bg-gradient-to-b from-teal/10 via-transparent to-transparent pointer-events-none"></div>
79− <div class="relative bg-surface-raised border border-rule rounded-xl overflow-hidden text-[13px]">
80−
81− <!-- Top bar -->
82− <div class="flex items-center justify-between px-5 py-3 border-b border-rule">
83− <div class="flex items-center gap-3">
84− <span class="font-display italic text-ink text-sm">executor</span>
85− <span class="text-ink-faint">/</span>
86− <span class="text-ink-muted">Sources</span>
87− <span class="text-ink-faint">/</span>
88− <span class="text-ink" data-breadcrumb>GitHub</span>
89− </div>
90− <div class="flex items-center gap-4">
91− <span class="font-mono text-[11px] text-teal" data-total-tools>482 tools indexed</span>
68+ <div class="grid sm:grid-cols-2 gap-5">
69+ {/* Every tool */}
70+ <div class="surface-card p-7 sm:p-8">
71+ <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
72+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" style="color: var(--color-accent)">
73+ <rect x="3" y="3" width="7" height="7" rx="1.5"/>
74+ <rect x="14" y="3" width="7" height="7" rx="1.5"/>
75+ <rect x="3" y="14" width="7" height="7" rx="1.5"/>
76+ <rect x="14" y="14" width="7" height="7" rx="1.5"/>
77+ </svg>
9278 </div>
79+ <h3 class="text-[18px] font-semibold tracking-[-0.01em] text-ink mb-2">Every tool</h3>
80+ <p class="text-[14.5px] leading-[1.55] text-ink-2 text-pretty">
81+ OpenAPI, GraphQL, MCP, CLI. Point Executor at any spec — it indexes, types, and exposes the tools.
82+ </p>
9383 </div>
9484
95− <!-- Content: sidebar + main -->
96− <div class="flex">
97−
98− <!-- Sidebar: sources -->
99− <div class="w-[200px] shrink-0 border-r border-rule p-3 space-y-1 hidden sm:block">
100− <div class="text-[10px] font-mono text-ink-faint uppercase tracking-[0.1em] mb-2.5 px-2">Connected</div>
101− <button data-source="github" class="catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors bg-teal/[0.08] border border-teal/15">
102− <div class="w-5 h-5 rounded bg-orange-400/15 flex items-center justify-center text-[9px] text-orange-400 font-mono font-medium shrink-0">G</div>
103− <div class="flex-1 min-w-0">
104− <div class="text-ink text-[12px] truncate">GitHub</div>
105− <div class="text-[10px] text-ink-faint">347 tools</div>
106− </div>
107− <span class="text-[9px] font-mono text-ink-faint shrink-0">openapi</span>
108− </button>
109− <button data-source="slack" class="catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors border border-transparent hover:bg-surface-overlay">
110− <div class="w-5 h-5 rounded bg-blue-400/15 flex items-center justify-center text-[9px] text-blue-400 font-mono font-medium shrink-0">S</div>
111− <div class="flex-1 min-w-0">
112− <div class="text-ink-muted text-[12px] truncate">Slack</div>
113− <div class="text-[10px] text-ink-faint">42 tools</div>
114− </div>
115− <span class="text-[9px] font-mono text-ink-faint shrink-0">openapi</span>
116− </button>
117− <button data-source="calendar" class="catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors border border-transparent hover:bg-surface-overlay">
118− <div class="w-5 h-5 rounded bg-green-400/15 flex items-center justify-center text-[9px] text-green-400 font-mono font-medium shrink-0">C</div>
119− <div class="flex-1 min-w-0">
120− <div class="text-ink-muted text-[12px] truncate">Calendar</div>
121− <div class="text-[10px] text-ink-faint">18 tools</div>
122− </div>
123− <span class="text-[9px] font-mono text-ink-faint shrink-0">google</span>
124− </button>
125− <button data-source="linear" class="catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors border border-transparent hover:bg-surface-overlay">
126− <div class="w-5 h-5 rounded bg-purple-400/15 flex items-center justify-center text-[9px] text-purple-400 font-mono font-medium shrink-0">L</div>
127− <div class="flex-1 min-w-0">
128− <div class="text-ink-muted text-[12px] truncate">Linear</div>
129− <div class="text-[10px] text-ink-faint">56 tools</div>
130− </div>
131− <span class="text-[9px] font-mono text-ink-faint shrink-0">graphql</span>
132− </button>
133− <button data-source="resend" class="catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors border border-transparent hover:bg-surface-overlay">
134− <div class="w-5 h-5 rounded bg-red-400/15 flex items-center justify-center text-[9px] text-red-400 font-mono font-medium shrink-0">R</div>
135− <div class="flex-1 min-w-0">
136− <div class="text-ink-muted text-[12px] truncate">Resend</div>
137− <div class="text-[10px] text-ink-faint">19 tools</div>
138− </div>
139− <span class="text-[9px] font-mono text-ink-faint shrink-0">openapi</span>
140− </button>
141− <button data-source="axiom" class="catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors border border-transparent hover:bg-surface-overlay">
142− <div class="w-5 h-5 rounded bg-cyan-400/15 flex items-center justify-center text-[9px] text-cyan-400 font-mono font-medium shrink-0">A</div>
143− <div class="flex-1 min-w-0">
144− <div class="text-ink-muted text-[12px] truncate">Axiom</div>
145− <div class="text-[10px] text-ink-faint">17 tools</div>
146− </div>
147− <span class="text-[9px] font-mono text-ink-faint shrink-0">mcp</span>
148− </button>
85+ {/* Every agent */}
86+ <div class="surface-card p-7 sm:p-8">
87+ <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
88+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" style="color: var(--color-accent)">
89+ <circle cx="9" cy="8" r="3"/>
90+ <circle cx="17" cy="9" r="2.5"/>
91+ <path d="M3 19c1-3.5 4-5 6-5s5 1.5 6 5"/>
92+ <path d="M14 19c.5-2 2.5-3 3-3s2.5 1 3 3"/>
93+ </svg>
14994 </div>
150−
151− <!-- Main: tool tree -->
152− <div class="flex-1 min-w-0 flex flex-col">
153− <!-- Search -->
154− <div class="px-4 py-3 border-b border-rule shrink-0">
155− <div class="flex items-center gap-2 px-3 py-2 rounded-md border border-rule bg-surface text-ink-faint text-[12px]">
156− <svg class="w-3.5 h-3.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" /></svg>
157− <span data-search-label>Search 347 tools...</span>
158− </div>
159− </div>
160−
161− <!-- Scrollable tool tree with bottom fade -->
162− <div class="relative flex-1 min-h-0">
163− <div class="overflow-y-auto h-[340px] scrollbar-thin" data-tool-list>
164− <!-- Populated by JS -->
165− </div>
166− <div class="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-surface-raised to-transparent pointer-events-none transition-opacity duration-300" data-fade></div>
167− </div>
168− </div>
169−
95+ <h3 class="text-[18px] font-semibold tracking-[-0.01em] text-ink mb-2">Every agent</h3>
96+ <p class="text-[14.5px] leading-[1.55] text-ink-2 text-pretty">
97+ Claude Code, Cursor, Codex, OpenCode, Windsurf, or your own. One catalog, no per-agent setup.
98+ </p>
17099 </div>
171− </div>
172− </div>
173−
174− <script>
175− const sources = {
176− github: {
177− name: 'GitHub', count: 347, tools: [
178− { name: 'issues.list', desc: 'List repository issues', policy: 'read', method: 'GET' },
179− { name: 'issues.create', desc: 'Create a new issue', policy: 'write', method: 'POST' },
180− { name: 'issues.get', desc: 'Get a single issue', policy: 'read', method: 'GET' },
181− { name: 'issues.update', desc: 'Update an issue', policy: 'write', method: 'PATCH' },
182− { name: 'issues.lock', desc: 'Lock an issue conversation', policy: 'write', method: 'PUT' },
183− { name: 'issues.comments.list', desc: 'List issue comments', policy: 'read', method: 'GET' },
184− { name: 'issues.comments.create', desc: 'Create an issue comment', policy: 'write', method: 'POST' },
185− { name: 'issues.labels.list', desc: 'List labels on an issue', policy: 'read', method: 'GET' },
186− { name: 'issues.labels.add', desc: 'Add labels to an issue', policy: 'write', method: 'POST' },
187− { name: 'pulls.list', desc: 'List pull requests', policy: 'read', method: 'GET' },
188− { name: 'pulls.get', desc: 'Get a pull request', policy: 'read', method: 'GET' },
189− { name: 'pulls.create', desc: 'Create a pull request', policy: 'write', method: 'POST' },
190− { name: 'pulls.merge', desc: 'Merge a pull request', policy: 'destructive', method: 'PUT' },
191− { name: 'pulls.reviews.list', desc: 'List reviews on a PR', policy: 'read', method: 'GET' },
192− { name: 'pulls.reviews.create', desc: 'Create a PR review', policy: 'write', method: 'POST' },
193− { name: 'repos.get', desc: 'Get a repository', policy: 'read', method: 'GET' },
194− { name: 'repos.list', desc: 'List user repositories', policy: 'read', method: 'GET' },
195− { name: 'repos.create', desc: 'Create a repository', policy: 'write', method: 'POST' },
196− { name: 'repos.delete', desc: 'Delete a repository', policy: 'destructive', method: 'DELETE' },
197− { name: 'actions.workflows.list', desc: 'List repository workflows', policy: 'read', method: 'GET' },
198− { name: 'actions.workflows.dispatch', desc: 'Trigger a workflow run', policy: 'write', method: 'POST' },
199− { name: 'actions.runs.list', desc: 'List workflow runs', policy: 'read', method: 'GET' },
200− { name: 'actions.runs.cancel', desc: 'Cancel a workflow run', policy: 'destructive', method: 'POST' },
201− ]
202− },
203− slack: {
204− name: 'Slack', count: 42, tools: [
205− { name: 'chat.postMessage', desc: 'Send a message to a channel', policy: 'write', method: 'POST' },
206− { name: 'chat.update', desc: 'Update an existing message', policy: 'write', method: 'POST' },
207− { name: 'chat.delete', desc: 'Delete a message', policy: 'destructive', method: 'POST' },
208− { name: 'conversations.list', desc: 'List all channels', policy: 'read', method: 'GET' },
209− { name: 'conversations.history', desc: 'Fetch message history', policy: 'read', method: 'GET' },
210− { name: 'conversations.create', desc: 'Create a new channel', policy: 'write', method: 'POST' },
211− { name: 'conversations.archive', desc: 'Archive a channel', policy: 'destructive', method: 'POST' },
212− { name: 'conversations.members', desc: 'List channel members', policy: 'read', method: 'GET' },
213− { name: 'users.list', desc: 'List all users in workspace', policy: 'read', method: 'GET' },
214− { name: 'users.info', desc: 'Get user profile info', policy: 'read', method: 'GET' },
215− { name: 'reactions.add', desc: 'Add a reaction to a message', policy: 'write', method: 'POST' },
216− { name: 'reactions.remove', desc: 'Remove a reaction', policy: 'write', method: 'POST' },
217− { name: 'files.upload', desc: 'Upload a file to a channel', policy: 'write', method: 'POST' },
218− { name: 'files.list', desc: 'List files in workspace', policy: 'read', method: 'GET' },
219− ]
220− },
221− calendar: {
222− name: 'Google Calendar', count: 18, tools: [
223− { name: 'events.list', desc: 'List upcoming events', policy: 'read', method: 'GET' },
224− { name: 'events.get', desc: 'Get a single event', policy: 'read', method: 'GET' },
225− { name: 'events.insert', desc: 'Create a new event', policy: 'write', method: 'POST' },
226− { name: 'events.update', desc: 'Update an existing event', policy: 'write', method: 'PATCH' },
227− { name: 'events.delete', desc: 'Delete an event', policy: 'destructive', method: 'DELETE' },
228− { name: 'events.move', desc: 'Move event to another calendar', policy: 'write', method: 'POST' },
229− { name: 'calendars.get', desc: 'Get calendar metadata', policy: 'read', method: 'GET' },
230− { name: 'calendars.insert', desc: 'Create a secondary calendar', policy: 'write', method: 'POST' },
231− { name: 'calendars.delete', desc: 'Delete a calendar', policy: 'destructive', method: 'DELETE' },
232− { name: 'calendarList.list', desc: 'List calendars on user list', policy: 'read', method: 'GET' },
233− { name: 'freebusy.query', desc: 'Check availability', policy: 'read', method: 'POST' },
234− { name: 'acl.list', desc: 'List access control rules', policy: 'read', method: 'GET' },
235− { name: 'acl.insert', desc: 'Add an access control rule', policy: 'write', method: 'POST' },
236− ]
237− },
238− linear: {
239− name: 'Linear', count: 56, tools: [
240− { name: 'issues.list', desc: 'List issues with filters', policy: 'read', method: 'query' },
241− { name: 'issues.get', desc: 'Get a single issue', policy: 'read', method: 'query' },
242− { name: 'issues.create', desc: 'Create a new issue', policy: 'write', method: 'mutation' },
243− { name: 'issues.update', desc: 'Update issue status or fields', policy: 'write', method: 'mutation' },
244− { name: 'issues.delete', desc: 'Delete an issue permanently', policy: 'destructive', method: 'mutation' },
245− { name: 'issues.archive', desc: 'Archive an issue', policy: 'write', method: 'mutation' },
246− { name: 'projects.list', desc: 'List all projects', policy: 'read', method: 'query' },
247− { name: 'projects.create', desc: 'Create a new project', policy: 'write', method: 'mutation' },
248− { name: 'projects.update', desc: 'Update project details', policy: 'write', method: 'mutation' },
249− { name: 'comments.list', desc: 'List comments on an issue', policy: 'read', method: 'query' },
250− { name: 'comments.create', desc: 'Add a comment to an issue', policy: 'write', method: 'mutation' },
251− { name: 'cycles.list', desc: 'List all cycles', policy: 'read', method: 'query' },
252− { name: 'teams.list', desc: 'List all teams', policy: 'read', method: 'query' },
253− { name: 'labels.list', desc: 'List all labels', policy: 'read', method: 'query' },
254− ]
255− },
256− resend: {
257− name: 'Resend', count: 19, tools: [
258− { name: 'emails.send', desc: 'Send a transactional email', policy: 'write', method: 'POST' },
259− { name: 'emails.get', desc: 'Get email delivery status', policy: 'read', method: 'GET' },
260− { name: 'emails.batch', desc: 'Send a batch of emails', policy: 'write', method: 'POST' },
261− { name: 'domains.list', desc: 'List verified domains', policy: 'read', method: 'GET' },
262− { name: 'domains.get', desc: 'Get domain details', policy: 'read', method: 'GET' },
263− { name: 'domains.create', desc: 'Add a new domain', policy: 'write', method: 'POST' },
264− { name: 'domains.verify', desc: 'Verify a domain', policy: 'write', method: 'POST' },
265− { name: 'domains.delete', desc: 'Remove a domain', policy: 'destructive', method: 'DELETE' },
266− { name: 'apiKeys.list', desc: 'List API keys', policy: 'read', method: 'GET' },
267− { name: 'apiKeys.create', desc: 'Create an API key', policy: 'write', method: 'POST' },
268− { name: 'contacts.list', desc: 'List contacts in audience', policy: 'read', method: 'GET' },
269− { name: 'contacts.create', desc: 'Create a new contact', policy: 'write', method: 'POST' },
270− { name: 'contacts.remove', desc: 'Remove a contact', policy: 'destructive', method: 'DELETE' },
271− ]
272− },
273− axiom: {
274− name: 'Axiom', count: 17, tools: [
275− { name: 'datasets.query', desc: 'Query datasets using APL', policy: 'read', method: 'tool' },
276− { name: 'datasets.list', desc: 'List all available datasets', policy: 'read', method: 'tool' },
277− { name: 'datasets.fields', desc: 'List all fields in a dataset', policy: 'read', method: 'tool' },
278− { name: 'datasets.info', desc: 'Get dataset metadata and stats', policy: 'read', method: 'tool' },
279− { name: 'datasets.create', desc: 'Create a new dataset', policy: 'write', method: 'tool' },
280− { name: 'datasets.trim', desc: 'Trim data from a dataset', policy: 'destructive', method: 'tool' },
281− { name: 'monitors.list', desc: 'List all monitors', policy: 'read', method: 'tool' },
282− { name: 'monitors.check', desc: 'Check monitor statuses', policy: 'read', method: 'tool' },
283− { name: 'monitors.create', desc: 'Create a new monitor', policy: 'write', method: 'tool' },
284− { name: 'dashboards.list', desc: 'List all dashboards', policy: 'read', method: 'tool' },
285− { name: 'dashboards.get', desc: 'Get dashboard by ID', policy: 'read', method: 'tool' },
286− { name: 'dashboards.create', desc: 'Create a new dashboard', policy: 'write', method: 'tool' },
287− { name: 'dashboards.delete', desc: 'Delete a dashboard by UID', policy: 'destructive', method: 'tool' },
288− { name: 'annotations.list', desc: 'List all annotations', policy: 'read', method: 'tool' },
289− { name: 'annotations.create', desc: 'Create an annotation', policy: 'write', method: 'tool' },
290− ]
291− },
292− };
293−
294− const policyColors = {
295− read: 'text-teal/60 bg-teal/[0.06]',
296− write: 'text-amber-300/60 bg-amber-300/[0.06]',
297− destructive: 'text-red-400/60 bg-red-400/[0.06]',
298− };
299−
300− const methodColors = {
301− GET: 'text-teal/60',
302− POST: 'text-amber-300/60',
303− PUT: 'text-amber-300/60',
304− PATCH: 'text-amber-300/60',
305− DELETE: 'text-red-400/60',
306− query: 'text-teal/60',
307− mutation: 'text-amber-300/60',
308− tool: 'text-ink-faint',
309− };
310−
311− // Tailwind safelist (must appear in markup for detection):
312− // text-teal/60 bg-teal/[0.06] text-amber-300/60 bg-amber-300/[0.06] text-red-400/60 bg-red-400/[0.06]
313−
314− const catalog = document.getElementById('hero-catalog');
315− const toolList = catalog.querySelector('[data-tool-list]');
316− const fadeEl = catalog.querySelector('[data-fade]');
317− const breadcrumb = catalog.querySelector('[data-breadcrumb]');
318− const totalTools = catalog.querySelector('[data-total-tools]');
319− const searchLabel = catalog.querySelector('[data-search-label]');
320−
321− function updateFade() {
322− const atBottom = toolList.scrollHeight - toolList.scrollTop - toolList.clientHeight < 8;
323− const noOverflow = toolList.scrollHeight <= toolList.clientHeight;
324− fadeEl.style.opacity = (atBottom || noOverflow) ? '0' : '1';
325− }
326− toolList.addEventListener('scroll', updateFade);
327− function renderSource(key) {
328− const src = sources[key];
329− breadcrumb.textContent = src.name;
330− searchLabel.textContent = `Search ${src.count} tools...`;
331−
332− const totalCount = Object.values(sources).reduce((s, v) => s + v.count, 0);
333− totalTools.textContent = `${totalCount} tools indexed`;
334−
335− catalog.querySelectorAll('.catalog-source').forEach(btn => {
336− const isActive = btn.dataset.source === key;
337− if (isActive) {
338− btn.className = btn.className
339− .replace('border-transparent', 'border-teal/15')
340− .replace('hover:bg-surface-overlay', 'bg-teal/[0.08]');
341− btn.querySelector('.min-w-0 > div:first-child').className = 'text-ink text-[12px] truncate';
342− } else {
343− btn.className = btn.className
344− .replace('border-teal/15', 'border-transparent')
345− .replace('bg-teal/[0.08]', 'hover:bg-surface-overlay');
346− btn.querySelector('.min-w-0 > div:first-child').className = 'text-ink-muted text-[12px] truncate';
347− }
348− });
349−
350− // Build tree from dot-separated tool names
351− const tree = {};
352− for (const tool of src.tools) {
353− const parts = tool.name.split('.');
354− let node = tree;
355− for (let i = 0; i < parts.length - 1; i++) {
356− if (!node[parts[i]]) node[parts[i]] = { _children: {} };
357− node = node[parts[i]]._children;
358− }
359− const leaf = parts[parts.length - 1];
360− node[leaf] = { _tool: tool };
361− }
362−
363− function renderTree(node, depth = 0) {
364− let html = '';
365− const keys = Object.keys(node);
366− keys.forEach((key, idx) => {
367− const entry = node[key];
368− const isLast = idx === keys.length - 1;
369− const indent = depth * 20;
370− const connector = isLast ? '└' : '├';
371−
372− if (entry._tool) {
373− // Leaf: actual tool
374− const tool = entry._tool;
375− const colors = policyColors[tool.policy];
376− const mColor = methodColors[tool.method] || 'text-ink-faint';
377− html += `<div class="flex items-center gap-3 py-1.5 pr-4 hover:bg-surface-overlay/50 transition-colors" style="padding-left:${16 + indent}px">
378− <span class="text-ink-faint font-mono text-[11px] w-3 shrink-0 select-none">${depth > 0 ? connector : '·'}</span>
379− <span class="font-mono text-[12px] text-ink flex-1 min-w-0 truncate">${key}</span>
380− <span class="text-[10px] font-mono ${colors} rounded px-1.5 py-0.5 shrink-0">${tool.policy}</span>
381− <span class="text-[10px] font-mono ${mColor} shrink-0 w-14 text-right">${tool.method}</span>
382− </div>`;
383− } else {
384− // Branch: namespace group
385− const childCount = Object.keys(entry._children).length;
386− html += `<div class="flex items-center gap-3 py-1.5 pr-4" style="padding-left:${16 + indent}px">
387− <span class="text-ink-faint font-mono text-[11px] w-3 shrink-0 select-none">${depth > 0 ? connector : '▸'}</span>
388− <span class="font-mono text-[12px] text-ink-muted">${key}</span>
389− <span class="text-[10px] text-ink-faint">${childCount}</span>
390− </div>`;
391− html += renderTree(entry._children, depth + 1);
392− }
393− });
394− return html;
395− }
396−
397− toolList.innerHTML = renderTree(tree);
398− toolList.scrollTop = 0;
399− updateFade();
400− }
401−
402− catalog.querySelectorAll('.catalog-source').forEach(btn => {
403− btn.addEventListener('click', () => renderSource(btn.dataset.source));
404− });
405−
406− renderSource('github');
407100
408− // ── Live detection ──
409− const detectInput = document.getElementById('detect-input');
410− const detectBtn = document.getElementById('detect-btn');
411− const detectStatus = document.getElementById('detect-status');
412− const detectExamples = document.getElementById('detect-examples');
413−
414− const kindColors = {
415− openapi: { bg: 'bg-emerald-400/15', text: 'text-emerald-400', letter: 'O' },
416− graphql: { bg: 'bg-pink-400/15', text: 'text-pink-400', letter: 'Q' },
417− mcp: { bg: 'bg-cyan-400/15', text: 'text-cyan-400', letter: 'M' },
418− googleDiscovery: { bg: 'bg-yellow-400/15', text: 'text-yellow-400', letter: 'G' },
419− };
420−
421− // Tailwind safelist for dynamic classes:
422− // bg-emerald-400/15 text-emerald-400 bg-pink-400/15 text-pink-400 bg-yellow-400/15 text-yellow-400
423−
424− const kindLabels = { openapi: 'openapi', graphql: 'graphql', mcp: 'mcp', googleDiscovery: 'google' };
425−
426− let detectedCount = 0;
427−
428− async function detectUrl(url) {
429− detectBtn.disabled = true;
430− detectBtn.textContent = 'Detecting...';
431− detectStatus.className = 'absolute left-0 right-0 -bottom-7 text-[11px] font-mono text-center text-ink-muted';
432− detectStatus.textContent = 'Probing URL...';
433−
434− try {
435− const res = await fetch('/api/detect', {
436− method: 'POST',
437− headers: { 'Content-Type': 'application/json' },
438− body: JSON.stringify({ url }),
439− });
440− const data = await res.json();
441−
442− if (!res.ok) {
443− detectStatus.className = 'absolute left-0 right-0 -bottom-7 text-[11px] font-mono text-center text-red-400/70';
444− detectStatus.textContent = data.error || 'Detection failed';
445− return;
446− }
447−
448− // Add detected source to the catalog
449− detectedCount++;
450− const key = `detected_${detectedCount}`;
451− const colors = kindColors[data.kind] || kindColors.openapi;
452− const kindLabel = kindLabels[data.kind] || data.kind;
453−
454− sources[key] = {
455− name: data.name,
456− count: data.count,
457− tools: data.tools.map(t => ({
458− name: t.name,
459− desc: t.desc,
460− policy: t.policy,
461− method: t.method,
462− })),
463− };
464−
465− // Add sidebar button
466− const sidebar = catalog.querySelector('.space-y-1');
467− const btn = document.createElement('button');
468− btn.dataset.source = key;
469− btn.className = 'catalog-source w-full flex items-center gap-2.5 px-2 py-2 rounded-md text-left transition-colors border border-transparent hover:bg-surface-overlay';
470− btn.innerHTML = `
471− <div class="w-5 h-5 rounded ${colors.bg} flex items-center justify-center text-[9px] ${colors.text} font-mono font-medium shrink-0">${data.name.charAt(0).toUpperCase()}</div>
472− <div class="flex-1 min-w-0">
473− <div class="text-ink-muted text-[12px] truncate">${data.name}</div>
474− <div class="text-[10px] text-ink-faint">${data.count} tools</div>
475− </div>
476− <span class="text-[9px] font-mono text-ink-faint shrink-0">${kindLabel}</span>
477− `;
478− btn.addEventListener('click', () => renderSource(key));
479− sidebar.appendChild(btn);
480−
481− renderSource(key);
482−
483− detectStatus.className = 'absolute left-0 right-0 -bottom-7 text-[11px] font-mono text-center text-teal';
484− detectStatus.textContent = `${data.kind} detected — ${data.count} tools found`;
485− detectExamples.classList.add('hidden');
486−
487− // Clear input
488− detectInput.value = '';
489− } catch (err) {
490− detectStatus.className = 'absolute left-0 right-0 -bottom-7 text-[11px] font-mono text-center text-red-400/70';
491− detectStatus.textContent = 'Network error — could not reach detection API';
492− } finally {
493− detectBtn.disabled = false;
494− detectBtn.textContent = 'Detect';
495− }
496− }
497−
498− detectBtn.addEventListener('click', () => {
499− const url = detectInput.value.trim();
500− if (url) detectUrl(url);
501− });
502−
503− detectInput.addEventListener('keydown', (e) => {
504− if (e.key === 'Enter') {
505− const url = detectInput.value.trim();
506− if (url) detectUrl(url);
507− }
508− });
509−
510− // Example buttons
511− document.querySelectorAll('.detect-example').forEach(btn => {
512− btn.addEventListener('click', () => {
513− detectInput.value = btn.dataset.url;
514− detectUrl(btn.dataset.url);
515− });
516− });
517− </script>
518− </div>
519−
520− <div class="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 text-ink-faint">
521− <span class="text-[10px] font-mono uppercase tracking-[0.2em]">Scroll</span>
522− <div class="w-px h-8 bg-gradient-to-b from-ink-faint/60 to-transparent"></div>
523− </div>
524− </section>
525−
526− <!-- ─── THE EVOLUTION: VISUAL DIAGRAM ─── -->
527− <section class="py-32 sm:py-40 relative">
528− <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
529− <div class="reveal mb-20">
530− <span class="text-[11px] font-mono uppercase tracking-[0.2em] text-ink-faint mb-4 block">The problem</span>
531− <h2 class="font-display text-[clamp(2rem,4.5vw,3.5rem)] leading-[1.05] tracking-tight">
532− Agents got smarter.<br /><span class="italic text-ink-muted">Their tooling didn't.</span>
533− </h2>
534− </div>
535−
536− <!-- Visual evolution: 3 columns showing the architecture change -->
537− <div class="reveal grid md:grid-cols-3 gap-6 md:gap-4">
538−
539− <!-- Era 1: Direct tool calls -->
540− <div class="rounded-xl border border-rule bg-surface-raised p-6 sm:p-8 flex flex-col">
541− <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em] mb-6">Era 1 &middot; Tool calling</span>
542−
543− <!-- Diagram -->
544− <div class="flex-1 flex flex-col items-center gap-3 my-6">
545− <div class="w-full rounded-lg border border-rule bg-surface px-4 py-3 text-center text-[13px] font-mono text-ink">Agent</div>
546− <!-- Fan-out arrows -->
547− <svg class="w-full h-12 text-red-400/50" viewBox="0 0 200 48" fill="none" stroke="currentColor" stroke-width="1.5">
548− <line x1="100" y1="0" x2="20" y2="48" />
549− <line x1="100" y1="0" x2="60" y2="48" />
550− <line x1="100" y1="0" x2="100" y2="48" />
551− <line x1="100" y1="0" x2="140" y2="48" />
552− <line x1="100" y1="0" x2="180" y2="48" />
553− </svg>
554− <div class="w-full grid grid-cols-5 gap-1">
555− <div class="rounded border border-red-400/20 bg-red-400/5 px-1 py-2 text-center text-[9px] font-mono text-red-400/60">tool</div>
556− <div class="rounded border border-red-400/20 bg-red-400/5 px-1 py-2 text-center text-[9px] font-mono text-red-400/60">tool</div>
557− <div class="rounded border border-red-400/20 bg-red-400/5 px-1 py-2 text-center text-[9px] font-mono text-red-400/60">tool</div>
558− <div class="rounded border border-red-400/20 bg-red-400/5 px-1 py-2 text-center text-[9px] font-mono text-red-400/60">tool</div>
559− <div class="rounded border border-red-400/20 bg-red-400/5 px-1 py-2 text-center text-[9px] font-mono text-red-400/60">tool</div>
101+ {/* Permission model */}
102+ <div class="surface-card p-7 sm:p-8">
103+ <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
104+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" style="color: var(--color-accent)">
105+ <path d="M12 3l8 3v6c0 4.5-3.4 8.4-8 9-4.6-.6-8-4.5-8-9V6l8-3z"/>
106+ <path d="M9 12l2 2 4-4"/>
107+ </svg>
560108 </div>
109+ <h3 class="text-[18px] font-semibold tracking-[-0.01em] text-ink mb-2">Permission model</h3>
110+ <p class="text-[14.5px] leading-[1.55] text-ink-2 text-pretty">
111+ Auto-run reads. Pause on writes. Block destructive. Wildcard rules. The fast path stays fast.
112+ </p>
561113 </div>
562114
563− <p class="text-[13px] text-ink-faint mt-auto">Every schema dumped into context. Tokens wasted. Poor performance.</p>
564− </div>
565−
566− <!-- Era 2: Bash -->
567− <div class="rounded-xl border border-rule bg-surface-raised p-6 sm:p-8 flex flex-col">
568− <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em] mb-6">Era 2 &middot; Bash</span>
569−
570− <div class="flex-1 flex flex-col items-center gap-3 my-6">
571− <div class="w-full rounded-lg border border-rule bg-surface px-4 py-3 text-center text-[13px] font-mono text-ink">Agent</div>
572− <svg class="w-full h-8 text-amber-400/50" viewBox="0 0 200 32" fill="none" stroke="currentColor" stroke-width="1.5">
573− <line x1="100" y1="0" x2="100" y2="32" />
574− </svg>
575− <div class="w-full rounded-lg border border-amber-400/30 bg-amber-400/5 px-4 py-3 text-center text-[13px] font-mono text-amber-400/80">bash</div>
576− <svg class="w-full h-8 text-amber-400/50" viewBox="0 0 200 32" fill="none" stroke="currentColor" stroke-width="1.5">
577− <line x1="60" y1="0" x2="20" y2="32" />
578− <line x1="100" y1="0" x2="100" y2="32" />
579− <line x1="140" y1="0" x2="180" y2="32" />
580− </svg>
581− <div class="w-full grid grid-cols-3 gap-1.5">
582− <div class="rounded border border-amber-400/20 bg-amber-400/5 px-1 py-2 text-center text-[9px] font-mono text-amber-400/60">CLI</div>
583− <div class="rounded border border-amber-400/20 bg-amber-400/5 px-1 py-2 text-center text-[9px] font-mono text-amber-400/60">CLI</div>
584− <div class="rounded border border-amber-400/20 bg-amber-400/5 px-1 py-2 text-center text-[9px] font-mono text-amber-400/60">CLI</div>
115+ {/* Auth */}
116+ <div class="surface-card p-7 sm:p-8">
117+ <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
118+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" style="color: var(--color-accent)">
119+ <circle cx="8" cy="14" r="4"/>
120+ <path d="M11 11l9-9"/>
121+ <path d="M16 6l3 3"/>
122+ <path d="M14 8l3 3"/>
123+ </svg>
585124 </div>
125+ <h3 class="text-[18px] font-semibold tracking-[-0.01em] text-ink mb-2">Auth that follows you</h3>
126+ <p class="text-[14.5px] leading-[1.55] text-ink-2 text-pretty">
127+ Sign in once. Every machine, every agent, and every teammate inherits your credentials.
128+ </p>
586129 </div>
587−
588− <p class="text-[13px] text-ink-faint mt-auto">Better, but a poor permission model that pushes users to dangerously-skip-permissions — or requires a VM.</p>
130+ </div>
131+ </div>
132+ </section>
133+
134+ {/* ─── THE ONES ─── */}
135+ <section class="py-24 sm:py-32 border-t border-rule">
136+ <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
137+ <div class="mb-14 max-w-2xl">
138+ <div class="font-mono text-[12px] uppercase tracking-[0.18em] text-ink-3 mb-4">Under the hood</div>
139+ <h2 class="text-[clamp(2.2rem,4.5vw,3.4rem)] font-semibold tracking-[-0.02em] leading-[1.05] text-ink max-w-[24ch] text-balance">
140+ One of <span class="serif-italic">each</span>.
141+ </h2>
589142 </div>
590143
591− <!-- Era 3: Executor -->
592− <div class="rounded-xl border border-teal/20 bg-surface-raised p-6 sm:p-8 flex flex-col relative overflow-hidden">
593− <div class="absolute inset-0 bg-gradient-to-b from-teal/[0.03] to-transparent pointer-events-none"></div>
594− <span class="relative font-mono text-[11px] text-teal uppercase tracking-[0.15em] mb-6">Era 3 &middot; Executor</span>
595−
596− <div class="relative flex-1 flex flex-col items-center gap-3 my-6">
597− <!-- Multiple agents -->
598− <div class="w-full grid grid-cols-3 gap-1.5">
599− <div class="rounded-lg border border-rule bg-surface px-2 py-2.5 text-center text-[10px] font-mono text-ink">Cursor</div>
600− <div class="rounded-lg border border-rule bg-surface px-2 py-2.5 text-center text-[10px] font-mono text-ink">Claude</div>
601− <div class="rounded-lg border border-rule bg-surface px-2 py-2.5 text-center text-[10px] font-mono text-ink">Custom</div>
602− </div>
603− <svg class="w-full h-8 text-teal/50" viewBox="0 0 200 32" fill="none" stroke="currentColor" stroke-width="1.5">
604− <line x1="33" y1="0" x2="100" y2="32" />
605− <line x1="100" y1="0" x2="100" y2="32" />
606− <line x1="167" y1="0" x2="100" y2="32" />
607− </svg>
608− <!-- Executor box with features -->
609− <div class="w-full rounded-lg border border-teal/30 bg-teal/[0.06] px-4 py-3">
610− <div class="text-center text-[13px] font-mono text-teal mb-2">executor</div>
611− <div class="flex justify-center gap-2">
612− <span class="text-[9px] font-mono text-teal/60 border border-teal/15 rounded px-1.5 py-0.5">auth</span>
613− <span class="text-[9px] font-mono text-teal/60 border border-teal/15 rounded px-1.5 py-0.5">policies</span>
614− <span class="text-[9px] font-mono text-teal/60 border border-teal/15 rounded px-1.5 py-0.5">catalog</span>
615− </div>
616− </div>
617− <svg class="w-full h-8 text-teal/50" viewBox="0 0 200 32" fill="none" stroke="currentColor" stroke-width="1.5">
618− <line x1="50" y1="0" x2="20" y2="32" />
619− <line x1="75" y1="0" x2="60" y2="32" />
620− <line x1="100" y1="0" x2="100" y2="32" />
621− <line x1="125" y1="0" x2="140" y2="32" />
622− <line x1="150" y1="0" x2="180" y2="32" />
623− </svg>
624− <div class="w-full grid grid-cols-5 gap-1">
625− <div class="rounded border border-teal/20 bg-teal/5 px-1 py-2 text-center text-[8px] font-mono text-teal/60">REST</div>
626− <div class="rounded border border-teal/20 bg-teal/5 px-1 py-2 text-center text-[8px] font-mono text-teal/60">GraphQL</div>
627− <div class="rounded border border-teal/20 bg-teal/5 px-1 py-2 text-center text-[8px] font-mono text-teal/60">MCP</div>
628− <div class="rounded border border-teal/20 bg-teal/5 px-1 py-2 text-center text-[8px] font-mono text-teal/60">gRPC</div>
629− <div class="rounded border border-teal/15 bg-teal/[0.02] px-1 py-2 text-center text-[8px] font-mono text-teal/40 italic">any</div>
630− </div>
144+ <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-x-10 gap-y-12">
145+ <div>
146+ <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">01</span>
147+ <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One catalog</h3>
148+ <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
149+ Every integration indexed once. Searchable, typed, discoverable from any agent.
150+ </p>
151+ </div>
152+ <div>
153+ <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">02</span>
154+ <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One auth layer</h3>
155+ <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
156+ Credentials live with Executor. Never pasted into a prompt or copied between machines.
157+ </p>
158+ </div>
159+ <div>
160+ <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">03</span>
161+ <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One policy set</h3>
162+ <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
163+ Same rules across every agent and every machine. Wildcard, version, and team scoped.
164+ </p>
165+ </div>
166+ <div>
167+ <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">04</span>
168+ <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One audit log</h3>
169+ <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
170+ Every tool call, approval, and block — one place to query, one source of truth.
171+ </p>
631172 </div>
632−
633− <p class="relative text-[13px] text-teal/70 mt-auto">Typed, sandboxed, shared across all agents.</p>
634173 </div>
635174 </div>
636− </div>
637− </section>
638−
639− <!-- ─── WHAT YOU GET: icon grid ─── -->
640− <section class="py-32 sm:py-40 relative">
641− <div class="absolute inset-0 border-t border-b border-rule"></div>
642−
643− <div class="relative max-w-[1200px] mx-auto px-6 sm:px-10">
644− <div class="reveal mb-16">
645− <span class="text-[11px] font-mono uppercase tracking-[0.2em] text-ink-faint mb-4 block">The stack</span>
646− <h2 class="font-display text-[clamp(2rem,4.5vw,3.5rem)] leading-[1.05] tracking-tight">
647− Everything in<br /><span class="italic text-teal">one place</span>
648− </h2>
649− </div>
650−
651− <div class="reveal grid sm:grid-cols-2 lg:grid-cols-3 gap-px bg-rule rounded-xl overflow-hidden">
652− <div class="bg-surface p-8">
653− <svg class="w-6 h-6 text-teal mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" /></svg>
654− <div class="text-[14px] font-medium mb-1">Unified catalog</div>
655− <p class="text-[13px] text-ink-muted">Every integration indexed into one typed, discoverable catalog.</p>
656− </div>
657− <div class="bg-surface p-8">
658− <svg class="w-6 h-6 text-teal mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" /></svg>
659− <div class="text-[14px] font-medium mb-1">Shared auth</div>
660− <p class="text-[13px] text-ink-muted">Sign in once. Every agent shares your credentials.</p>
661− </div>
662− <div class="bg-surface p-8">
663− <svg class="w-6 h-6 text-teal mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /></svg>
664− <div class="text-[14px] font-medium mb-1">Policies</div>
665− <p class="text-[13px] text-ink-muted">Auto-approve reads. Pause on writes. Wildcard rules.</p>
175+ </section>
176+
177+ {/* ─── ROADMAP ─── */}
178+ <section id="roadmap" class="py-24 sm:py-32 border-t border-rule">
179+ <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
180+ <div class="mb-16 max-w-2xl">
181+ <div class="font-mono text-[12px] uppercase tracking-[0.18em] text-ink-3 mb-4">Roadmap</div>
182+ <h2 class="text-[clamp(2.2rem,4.5vw,3.4rem)] font-semibold tracking-[-0.02em] leading-[1.05] text-ink max-w-[24ch] text-balance">
183+ Where it's <span class="serif-italic">going</span>.
184+ </h2>
666185 </div>
667− <div class="bg-surface p-8">
668− <svg class="w-6 h-6 text-teal mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" /></svg>
669− <div class="text-[14px] font-medium mb-1">Any agent via MCP</div>
670− <p class="text-[13px] text-ink-muted">Executor is an MCP server. Point any agent at it.</p>
671− </div>
672− <div class="bg-surface p-8">
673− <svg class="w-6 h-6 text-teal mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /></svg>
674− <div class="text-[14px] font-medium mb-1">Local-first</div>
675− <p class="text-[13px] text-ink-muted">Secrets in your keychain. Nothing leaves your machine.</p>
676− </div>
677− <div class="bg-surface p-8">
678− <svg class="w-6 h-6 text-teal mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" /></svg>
679− <div class="text-[14px] font-medium mb-1">Intent-based discovery</div>
680− <p class="text-[13px] text-ink-muted">Agents search by what they need, not memorized paths.</p>
681− </div>
682− </div>
683− </div>
684− </section>
685−
686−
687− <!-- ─── ROADMAP ─── -->
688− <section id="roadmap" class="py-32 sm:py-40 relative">
689− <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
690− <div class="reveal mb-16">
691− <span class="text-[11px] font-mono uppercase tracking-[0.2em] text-ink-faint mb-4 block">Roadmap</span>
692− <h2 class="font-display text-[clamp(2rem,4.5vw,3.5rem)] leading-[1.05] tracking-tight">
693− What's <span class="italic text-teal">next</span>
694− </h2>
695− </div>
696186
697− <div class="reveal grid md:grid-cols-3 gap-6">
698− <!-- Now -->
699− <div class="rounded-xl border border-teal/20 p-6 sm:p-8 relative overflow-hidden">
700− <div class="absolute inset-0 bg-gradient-to-b from-teal/[0.03] to-transparent pointer-events-none"></div>
701− <div class="relative">
187+ <div class="grid md:grid-cols-3 gap-6">
188+ {/* Now */}
189+ <div class="surface-card p-7" style="border-color: rgba(99,91,255,0.25)">
702190 <div class="flex items-center gap-2 mb-5">
703− <div class="w-2 h-2 rounded-full bg-teal shadow-[0_0_6px_rgba(61,216,181,0.4)]"></div>
704− <span class="font-mono text-[11px] text-teal uppercase tracking-[0.15em]">Now</span>
191+ <span class="size-1.5 rounded-full" style="background: var(--color-accent); box-shadow: 0 0 0 4px rgba(99,91,255,0.12)"></span>
192+ <span class="font-mono text-[11px] uppercase tracking-[0.18em]" style="color: var(--color-accent)">Now</span>
705193 </div>
706− <ul class="space-y-3 text-[14px]">
707− <li class="flex items-start gap-2.5">
708− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
709− <span class="text-ink-muted">Core SDK &amp; CLI</span>
710− </li>
711− <li class="flex items-start gap-2.5">
712− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
713− <span class="text-ink-muted">MCP bridge</span>
714− </li>
715− <li class="flex items-start gap-2.5">
716− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
717− <span class="text-ink-muted">Policy engine</span>
718− </li>
719− <li class="flex items-start gap-2.5">
720− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
721− <span class="text-ink-muted">Local web UI</span>
722− </li>
723− <li class="flex items-start gap-2.5">
724− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
725− <span class="text-ink-muted">Desktop app</span>
726− </li>
727− <li class="flex items-start gap-2.5">
728− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
729− <span class="text-ink-muted">Team management &amp; SaaS</span>
730− </li>
194+ <ul role="list" class="space-y-2.5 text-[14px] text-ink-2">
195+ <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Core SDK &amp; CLI</span></li>
196+ <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>MCP bridge</span></li>
197+ <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Permission engine</span></li>
198+ <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Local web UI</span></li>
199+ <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Desktop app</span></li>
200+ <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Cloud &amp; team management</span></li>
731201 </ul>
732202 </div>
733− </div>
734203
735− <!-- Soon -->
736− <div class="rounded-xl border border-rule p-6 sm:p-8">
737− <div class="flex items-center gap-2 mb-5">
738− <div class="w-2 h-2 rounded-full bg-ink-faint"></div>
739− <span class="font-mono text-[11px] text-ink-muted uppercase tracking-[0.15em]">Soon</span>
204+ {/* Soon */}
205+ <div class="surface-card p-7">
206+ <div class="flex items-center gap-2 mb-5">
207+ <span class="size-1.5 rounded-full bg-ink-3"></span>
208+ <span class="font-mono text-[11px] uppercase tracking-[0.18em] text-ink-2">Soon</span>
209+ </div>
210+ <ul role="list" class="space-y-2.5 text-[14px] text-ink-2">
211+ <li>Skills</li>
212+ <li>Generative UI</li>
213+ <li>Workflows</li>
214+ <li>Workspaces</li>
215+ </ul>
740216 </div>
741− <ul class="space-y-3 text-[14px]">
742− <li class="text-ink-muted">Skills</li>
743− <li class="text-ink-muted">Generative UI</li>
744− <li class="text-ink-muted">Workflows</li>
745− <li class="text-ink-muted">Workspaces</li>
746− </ul>
747− </div>
748217
749− <!-- Later -->
750− <div class="rounded-xl border border-rule/50 p-6 sm:p-8">
751− <div class="flex items-center gap-2 mb-5">
752− <div class="w-2 h-2 rounded-full bg-ink-faint/40"></div>
753− <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em]">Later</span>
218+ {/* Later */}
219+ <div class="surface-card p-7" style="opacity: 0.85">
220+ <div class="flex items-center gap-2 mb-5">
221+ <span class="size-1.5 rounded-full" style="background: rgba(13,13,16,0.25)"></span>
222+ <span class="font-mono text-[11px] uppercase tracking-[0.18em] text-ink-3">Later</span>
223+ </div>
224+ <ul role="list" class="space-y-2.5 text-[14px] text-ink-3">
225+ <li>Customer-managed integrations</li>
226+ <li>Webhooks &amp; crons</li>
227+ <li>Virtual filesystems &amp; KV</li>
228+ <li>npm ecosystem support</li>
229+ </ul>
754230 </div>
755− <ul class="space-y-3 text-[14px]">
756− <li class="text-ink-faint">Customer-managed integrations</li>
757− <li class="text-ink-faint">Workflow primitives (webhooks, crons)</li>
758− <li class="text-ink-faint">Virtual filesystems &amp; KV stores</li>
759− <li class="text-ink-faint">npm ecosystem support</li>
760− </ul>
761231 </div>
762232 </div>
763− </div>
764− </section>
765−
766− <!-- ─── GET STARTED: dual path ─── -->
767− <section id="get-started" class="py-32 sm:py-40 relative">
768− <div class="absolute inset-0 border-t border-rule"></div>
769−
770− <div class="relative max-w-[1200px] mx-auto px-6 sm:px-10">
771− <div class="reveal max-w-2xl mx-auto text-center mb-16">
772− <span class="text-[11px] font-mono uppercase tracking-[0.2em] text-ink-faint mb-4 block">Get started</span>
773− <h2 class="font-display text-[clamp(2rem,5vw,4rem)] leading-[1.05] tracking-tight mb-5">
774− Pick your <span class="italic text-teal">path</span>
775− </h2>
776− <p class="text-[15px] leading-[1.6] text-ink-muted">
777− Hosted Executor for teams, or open source on your own machine.
778− </p>
779− </div>
780−
781− <!-- Dual-path cards -->
782− <div class="reveal grid md:grid-cols-2 gap-6 items-stretch" id="install">
233+ </section>
234+
235+ {/* ─── GET STARTED ─── */}
236+ <section id="install" class="py-24 sm:py-32 border-t border-rule">
237+ <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
238+ <div class="mb-14 mx-auto text-center max-w-2xl">
239+ <div class="font-mono text-[12px] uppercase tracking-[0.18em] text-ink-3 mb-4">Get started</div>
240+ <h2 class="text-[clamp(2.2rem,4.5vw,3.4rem)] font-semibold tracking-[-0.02em] leading-[1.05] text-ink mx-auto max-w-[20ch] text-balance">
241+ Pick your <span class="serif-italic">path</span>.
242+ </h2>
243+ </div>
783244
784− <!-- Cloud (recommended) -->
785− <div class="relative rounded-xl border border-teal/25 bg-surface-raised p-6 sm:p-8 flex flex-col justify-between overflow-hidden">
786− <div class="absolute inset-0 bg-gradient-to-b from-teal/[0.04] to-transparent pointer-events-none"></div>
787− <div class="relative">
788− <div class="flex items-center justify-between mb-5">
789− <span class="font-mono text-[11px] text-teal uppercase tracking-[0.15em]">Cloud</span>
790− <span class="text-[10px] font-mono text-teal border border-teal/25 rounded px-2 py-0.5 uppercase tracking-[0.1em]">Recommended for teams</span>
245+ <div class="grid md:grid-cols-2 gap-6 max-w-[920px] mx-auto">
246+ {/* Cloud */}
247+ <div class="surface-card p-8 flex flex-col" style="border-color: rgba(99,91,255,0.25)">
248+ <div class="flex items-center gap-2 mb-4">
249+ <span class="font-mono text-[11px] uppercase tracking-[0.18em]" style="color: var(--color-accent)">Recommended</span>
791250 </div>
792−
793− <h3 class="font-display text-[clamp(1.5rem,2.6vw,2rem)] leading-[1.1] tracking-tight text-ink mb-3">
794− Run it with <span class="italic text-teal">your team</span>
795− </h3>
796− <p class="text-[14px] leading-[1.6] text-ink-muted mb-6">
797− A managed Executor for your whole org. Shared sources, shared auth, shared policies &mdash; onboard a teammate in seconds.
251+ <h3 class="text-[22px] font-semibold tracking-[-0.01em] text-ink mb-3">Cloud</h3>
252+ <p class="text-[14.5px] leading-[1.55] text-ink-2 mb-6 text-pretty flex-1">
253+ Hosted Executor. Auth, sync, policies, and the whole team online in five minutes. Free tier to start.
798254 </p>
799−
800− <ul role="list" class="space-y-2.5 text-[13px] mb-8">
801− <li class="flex items-start gap-2.5">
802− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
803− <span class="text-ink-muted">Free for up to 3 members</span>
804− </li>
805− <li class="flex items-start gap-2.5">
806− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
807− <span class="text-ink-muted">Org-wide catalog &amp; policy engine</span>
808− </li>
809− <li class="flex items-start gap-2.5">
810− <svg class="w-4 h-4 text-teal mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
811− <span class="text-ink-muted">Zero infra &mdash; sign in and go</span>
812− </li>
813− </ul>
255+ <a href="/cloud" class="btn-primary self-start">Try Cloud →</a>
814256 </div>
815257
816− <div class="relative">
817− <a href="/cloud" class="inline-flex w-full items-center justify-center gap-2 text-[14px] font-medium text-surface bg-teal hover:bg-teal-dim pl-5 pr-3 py-3 rounded-md transition-colors">
818− Try Cloud
819− <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>
820− </a>
821− </div>
822− </div>
823−
824− <!-- Local (open source) -->
825− <div class="rounded-xl border border-rule bg-surface-raised p-6 sm:p-8 flex flex-col justify-between">
826− <div>
827− <div class="flex items-center justify-between mb-5">
828− <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em]">Local</span>
829− <span class="text-[10px] font-mono text-ink-faint border border-rule rounded px-2 py-0.5 uppercase tracking-[0.1em]">Open source</span>
258+ {/* Local */}
259+ <div class="surface-card p-8 flex flex-col">
260+ <div class="flex items-center gap-2 mb-4">
261+ <span class="font-mono text-[11px] uppercase tracking-[0.18em] text-ink-3">Open source</span>
830262 </div>
831−
832− <h3 class="font-display text-[clamp(1.5rem,2.6vw,2rem)] leading-[1.1] tracking-tight text-ink mb-3">
833− Run it on <span class="italic text-ink-muted">your laptop</span>
834− </h3>
835− <p class="text-[14px] leading-[1.6] text-ink-muted mb-6">
836− The same Executor, MIT-licensed, running entirely on your machine. Secrets stay in your keychain. Nothing leaves your laptop.
263+ <h3 class="text-[22px] font-semibold tracking-[-0.01em] text-ink mb-3">Local</h3>
264+ <p class="text-[14.5px] leading-[1.55] text-ink-2 mb-6 text-pretty flex-1">
265+ Run on your own machine. Keychain-backed secrets. Zero data leaves your laptop. MIT licensed.
837266 </p>
838−
839− <!-- MCP installation steps -->
840− <div class="space-y-4">
841− <div>
842− <p class="text-[11px] font-medium text-ink-muted uppercase tracking-wider mb-2">1. Install</p>
843− <div class="flex border border-rule border-b-0 rounded-t-lg overflow-hidden bg-surface">
844− <button data-pm="npm" class="pm-tab pm-tab-active flex-1 text-[12px] font-medium py-2 border-b-2 transition-colors">npm</button>
845− <button data-pm="bun" class="pm-tab flex-1 text-[12px] font-medium py-2 border-b-2 border-transparent text-ink-faint hover:text-ink transition-colors">bun</button>
846− <button data-pm="pnpm" class="pm-tab flex-1 text-[12px] font-medium py-2 border-b-2 border-transparent text-ink-faint hover:text-ink transition-colors">pnpm</button>
847− </div>
848− <div class="install-cmd relative bg-surface border border-rule rounded-b-lg px-4 py-2.5 font-mono text-[13px]">
849− <span class="text-ink-faint select-none">$ </span><span class="cmd-text text-ink">npm install -g executor</span>
850− <button class="copy-btn absolute right-2.5 top-1/2 -translate-y-1/2 text-ink-faint hover:text-ink transition-colors p-1" aria-label="Copy">
851− <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9.75a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" /></svg>
852− </button>
853− </div>
854− </div>
855−
856− <div>
857− <p class="text-[11px] font-medium text-ink-muted uppercase tracking-wider mb-2">2. Add to your agent</p>
858− <div class="flex border border-rule border-b-0 rounded-t-lg overflow-hidden bg-surface">
859− <button data-runner="npx" class="runner-tab runner-tab-active flex-1 text-[12px] font-medium py-2 border-b-2 transition-colors">npx</button>
860− <button data-runner="bunx" class="runner-tab flex-1 text-[12px] font-medium py-2 border-b-2 border-transparent text-ink-faint hover:text-ink transition-colors">bunx</button>
861− <button data-runner="pnpx" class="runner-tab flex-1 text-[12px] font-medium py-2 border-b-2 border-transparent text-ink-faint hover:text-ink transition-colors">pnpx</button>
862− </div>
863− <div class="relative bg-surface border border-rule rounded-b-lg px-4 py-2.5 font-mono text-[13px]">
864− <span class="text-ink-faint select-none">$ </span><span class="runner-cmd-text text-ink">npx add-mcp "executor mcp" --name "executor"</span>
865− <button class="copy-btn absolute right-2.5 top-1/2 -translate-y-1/2 text-ink-faint hover:text-ink transition-colors p-1" aria-label="Copy">
866− <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9.75a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" /></svg>
867− </button>
868− </div>
869− </div>
870−
871− <div>
872− <p class="text-[11px] font-medium text-ink-muted uppercase tracking-wider mb-2">3. Open the dashboard</p>
873− <div class="relative bg-surface border border-rule rounded-lg px-4 py-2.5 font-mono text-[13px]">
874− <span class="text-ink-faint select-none">$ </span><span class="text-ink">executor web</span>
875− <button class="copy-btn absolute right-2.5 top-1/2 -translate-y-1/2 text-ink-faint hover:text-ink transition-colors p-1" aria-label="Copy">
876− <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9.75a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" /></svg>
877− </button>
878− </div>
879− </div>
267+ <div class="font-mono text-[13px] text-ink bg-surface-2 border border-rule px-3 py-2 rounded-md self-start" style="background: var(--color-surface-2)">
268+ <span class="text-ink-3">$</span> npx executor web
880269 </div>
881270 </div>
882−
883− <div class="mt-8">
884− <a href="https://github.com/RhysSullivan/executor" class="inline-flex w-full items-center justify-center gap-2 text-[14px] font-medium text-ink-muted hover:text-ink border border-rule hover:border-ink-faint px-5 py-3 rounded-md transition-colors">
885− <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
886− View on GitHub
887− </a>
888− </div>
889271 </div>
890272 </div>
891− </div>
892− </section>
893−
894− <script>
895− // Package manager tabs
896− const cmds: Record<string, string> = {
897− npm: 'npm install -g executor',
898− bun: 'bun add -g executor',
899− pnpm: 'pnpm add -g executor',
900− };
901− const tabs = document.querySelectorAll<HTMLButtonElement>('.pm-tab');
902− const cmdText = document.querySelector('.cmd-text');
903− tabs.forEach(tab => {
904− tab.addEventListener('click', () => {
905− tabs.forEach(t => {
906− t.classList.remove('pm-tab-active', 'text-teal', 'border-teal');
907− t.classList.add('border-transparent', 'text-ink-faint');
908− });
909− tab.classList.add('pm-tab-active', 'text-teal', 'border-teal');
910− tab.classList.remove('border-transparent', 'text-ink-faint');
911− if (cmdText) cmdText.textContent = cmds[tab.dataset.pm!] || cmds.npm;
912− });
913− });
914− // Set initial active state
915− const initial = document.querySelector('.pm-tab-active');
916− if (initial) {
917− initial.classList.add('text-teal', 'border-teal');
918− }
919−
920− // Runner tabs (npx/bunx/pnpx)
921− const runnerCmds: Record<string, string> = {
922− npx: 'npx add-mcp "executor mcp" --name "executor"',
923− bunx: 'bunx add-mcp "executor mcp" --name "executor"',
924− pnpx: 'pnpx add-mcp "executor mcp" --name "executor"',
925− };
926− const runnerTabs = document.querySelectorAll<HTMLButtonElement>('.runner-tab');
927− const runnerCmdText = document.querySelector('.runner-cmd-text');
928− runnerTabs.forEach(tab => {
929− tab.addEventListener('click', () => {
930− runnerTabs.forEach(t => {
931− t.classList.remove('runner-tab-active', 'text-teal', 'border-teal');
932− t.classList.add('border-transparent', 'text-ink-faint');
933− });
934− tab.classList.add('runner-tab-active', 'text-teal', 'border-teal');
935− tab.classList.remove('border-transparent', 'text-ink-faint');
936− if (runnerCmdText) runnerCmdText.textContent = runnerCmds[tab.dataset.runner!] || runnerCmds.npx;
937− });
938− });
939− const initialRunner = document.querySelector('.runner-tab-active');
940− if (initialRunner) {
941− initialRunner.classList.add('text-teal', 'border-teal');
942− }
943−
944− // Copy buttons
945− document.querySelectorAll<HTMLButtonElement>('.copy-btn').forEach(btn => {
946− btn.addEventListener('click', () => {
947− const block = btn.closest('div');
948− const cmdEl = block?.querySelector('.cmd-text') || block?.querySelector('.runner-cmd-text') || block?.querySelector('.text-ink');
949− const text = cmdEl?.textContent?.trim();
950− if (!text) return;
951− navigator.clipboard.writeText(text);
952− const svg = btn.querySelector('svg')!;
953− const orig = svg.innerHTML;
954− svg.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />';
955− setTimeout(() => { svg.innerHTML = orig; }, 1500);
956− });
957− });
958− </script>
959−
960− <!-- ─── FOOTER ─── -->
961− <footer class="border-t border-rule py-10">
962− <div class="max-w-[1200px] mx-auto px-6 sm:px-10 flex items-center justify-between">
963− <span class="font-display italic text-ink-faint text-sm">executor</span>
964− <div class="flex items-center gap-5 text-[13px]">
965− <a href="/privacy" class="text-ink-faint hover:text-ink transition-colors">Privacy</a>
966− <a href="/terms" class="text-ink-faint hover:text-ink transition-colors">Terms</a>
967− <a href="https://github.com/RhysSullivan/executor" class="text-ink-faint hover:text-ink transition-colors">GitHub</a>
273+ </section>
274+
275+ {/* ─── FOOTER ─── */}
276+ <footer class="border-t border-rule py-10">
277+ <div class="max-w-[1200px] mx-auto px-6 sm:px-10 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6">
278+ <div class="flex items-center gap-2.5">
279+ <img src="/favicon-192.png" alt="" class="w-4 h-4 opacity-80" />
280+ <span class="text-[13px] font-medium text-ink-2">executor</span>
281+ <span class="text-[12px] text-ink-3 ml-2">© {new Date().getFullYear()}</span>
282+ </div>
283+ <div class="flex items-center gap-6 text-[13px] text-ink-2">
284+ <a href="https://github.com/RhysSullivan/executor" class="hover:text-ink transition-colors">GitHub</a>
285+ <a href="/privacy" class="hover:text-ink transition-colors">Privacy</a>
286+ <a href="/terms" class="hover:text-ink transition-colors">Terms</a>
287+ </div>
968288 </div>
289+ </footer>
290+
969291 </div>
970− </footer>
292+ </main>
971293 </Layout>
apps/marketing/src/pages/setup.astro+0−172
@@ -1,172 +0,0 @@
1−---
2−export const prerender = true
3−import Layout from '../layouts/Layout.astro'
4−---
5−
6−<Layout title="Setup Executor" description="Get started with Executor — download the desktop app or install via CLI.">
7− <!-- ─── NAV ─── -->
8− <nav class="fixed top-0 w-full z-50 bg-surface/80 backdrop-blur-lg">
9− <div class="max-w-[1200px] mx-auto px-6 sm:px-10 h-14 flex items-center justify-between border-b border-rule">
10− <a href="/" class="font-display text-xl italic text-ink tracking-tight hover:text-teal transition-colors">executor</a>
11− <div class="flex items-center gap-8">
12− <a href="/setup" class="hidden sm:block text-[13px] text-ink hover:text-ink transition-colors">Setup</a>
13− <a href="https://github.com/RhysSullivan/executor" class="hidden sm:block text-[13px] text-ink-muted hover:text-ink transition-colors">GitHub</a>
14− </div>
15− </div>
16− </nav>
17−
18− <main class="pt-14">
19− <section class="py-24 sm:py-32 relative">
20− <div class="absolute top-[20%] left-[50%] -translate-x-1/2 w-[600px] h-[400px] bg-teal/[0.03] rounded-full blur-[150px] pointer-events-none"></div>
21−
22− <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
23− <div class="text-center max-w-2xl mx-auto mb-16">
24− <h1 class="font-display text-[clamp(2.5rem,5vw,4rem)] leading-[1.05] tracking-tight mb-6">
25− Get started with <span class="italic text-teal">Executor</span>
26− </h1>
27− <p class="text-[16px] leading-[1.7] text-ink-muted">
28− Manage sources, secrets, and tools from a native desktop app &mdash; or run straight from the command line.
29− </p>
30− </div>
31−
32− <!-- ─── PRIMARY: auto-detected download ─── -->
33− <div class="max-w-md mx-auto text-center mb-6" id="primary-download">
34− <a id="primary-btn" href="#" class="inline-flex items-center gap-2.5 text-[15px] font-medium text-surface bg-teal hover:bg-teal-dim px-8 py-3.5 rounded-lg transition-colors">
35− <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 14l-7 7m0 0l-7-7m7 7V3"/></svg>
36− <span id="primary-label">Download for macOS</span>
37− </a>
38− <p class="mt-3 text-[12px] text-ink-faint" id="primary-meta">Apple Silicon &amp; Intel &middot; .dmg</p>
39− </div>
40−
41− <!-- Other platforms link -->
42− <div class="text-center mb-20">
43− <button onclick="document.getElementById('all-platforms').classList.toggle('hidden')" class="text-[13px] text-ink-faint hover:text-ink transition-colors underline underline-offset-2 decoration-rule hover:decoration-ink-faint">
44− Other platforms
45− </button>
46− </div>
47−
48− <!-- ─── EXPANDED: all platforms (hidden by default) ─── -->
49− <div class="hidden max-w-2xl mx-auto mb-20" id="all-platforms">
50− <div class="grid sm:grid-cols-3 gap-4">
51− <a id="link-mac" href="#" class="group rounded-xl border border-rule hover:border-teal/30 p-5 text-center transition-all">
52− <div class="text-[14px] font-medium text-ink mb-1">macOS</div>
53− <p class="text-[11px] text-ink-faint">Apple Silicon &amp; Intel &middot; .dmg</p>
54− </a>
55− <a id="link-win" href="#" class="group rounded-xl border border-rule hover:border-teal/30 p-5 text-center transition-all">
56− <div class="text-[14px] font-medium text-ink mb-1">Windows</div>
57− <p class="text-[11px] text-ink-faint">Windows 10+ &middot; .exe</p>
58− </a>
59− <a id="link-linux" href="#" class="group rounded-xl border border-rule hover:border-teal/30 p-5 text-center transition-all">
60− <div class="text-[14px] font-medium text-ink mb-1">Linux</div>
61− <p class="text-[11px] text-ink-faint">Linux x64 &middot; .AppImage</p>
62− </a>
63− </div>
64− </div>
65−
66− <!-- ─── CLI alternative ─── -->
67− <div class="max-w-md mx-auto">
68− <div class="flex items-center justify-center gap-3 mb-6">
69− <div class="h-px flex-1 bg-rule"></div>
70− <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em]">Or via CLI</span>
71− <div class="h-px flex-1 bg-rule"></div>
72− </div>
73−
74− <button onclick="copyCommand('npx executor web')" class="group w-full flex items-center justify-between gap-3 bg-surface-raised border border-rule rounded-lg px-4 py-3 font-mono text-[13px] hover:border-ink-faint transition-colors text-left">
75− <div>
76− <span class="text-ink-faint select-none">$ </span><span class="text-ink">npx executor web</span>
77− </div>
78− <svg class="w-4 h-4 text-ink-faint group-hover:text-ink transition-colors shrink-0 copy-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
79− <svg class="w-4 h-4 text-teal shrink-0 check-icon hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>
80− </button>
81− </div>
82− </div>
83− </section>
84− </main>
85−
86− <!-- ─── FOOTER ─── -->
87− <footer class="border-t border-rule py-10">
88− <div class="max-w-[1200px] mx-auto px-6 sm:px-10 flex items-center justify-between">
89− <span class="font-display italic text-ink-faint text-sm">executor</span>
90− <div class="flex items-center gap-5 text-[13px]">
91− <a href="/privacy" class="text-ink-faint hover:text-ink transition-colors">Privacy</a>
92− <a href="/terms" class="text-ink-faint hover:text-ink transition-colors">Terms</a>
93− <a href="https://github.com/RhysSullivan/executor" class="text-ink-faint hover:text-ink transition-colors">GitHub</a>
94− </div>
95− </div>
96− </footer>
97−
98− <script is:inline>
99− const REPO = 'RhysSullivan/executor';
100− const RELEASE_API = `https://api.github.com/repos/${REPO}/releases/latest`;
101−
102− const PLATFORMS = {
103− mac: { label: 'Download for macOS', meta: 'Apple Silicon & Intel \u00b7 .dmg' },
104− win: { label: 'Download for Windows', meta: 'Windows 10+ \u00b7 .exe' },
105− linux: { label: 'Download for Linux', meta: 'Linux x64 \u00b7 .AppImage' },
106− };
107−
108− function detectOS() {
109− const ua = navigator.userAgent.toLowerCase();
110− if (ua.includes('mac')) return 'mac';
111− if (ua.includes('win')) return 'win';
112− return 'linux';
113− }
114−
115− async function setup() {
116− const userOS = detectOS();
117− const platform = PLATFORMS[userOS];
118−
119− // Set primary button text immediately
120− document.getElementById('primary-label').textContent = platform.label;
121− document.getElementById('primary-meta').textContent = platform.meta;
122−
123− try {
124− const res = await fetch(RELEASE_API);
125− if (!res.ok) return;
126− const release = await res.json();
127− const assets = release.assets || [];
128−
129− const find = (pattern) => assets.find(a => pattern.test(a.name));
130−
131− // Match: .dmg for macOS (arm64 preferred on Apple Silicon), .exe for Windows, .AppImage for Linux
132− const isAppleSilicon = navigator.userAgent.includes('Mac') &&
133− (navigator.userAgent.includes('ARM') || navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 0);
134− const macAsset = find(isAppleSilicon ? /arm64\.dmg$/ : /x64\.dmg$/) || find(/\.dmg$/);
135− const macUrl = macAsset?.browser_download_url;
136− const winUrl = find(/\.exe$/)?.browser_download_url;
137− const linuxUrl = find(/\.AppImage$/)?.browser_download_url;
138−
139− const urls = { mac: macUrl, win: winUrl, linux: linuxUrl };
140−
141− // Set primary button to user's OS
142− if (urls[userOS]) {
143− document.getElementById('primary-btn').href = urls[userOS];
144− }
145−
146− // Set "other platforms" links
147− if (macUrl) document.getElementById('link-mac').href = macUrl;
148− if (winUrl) document.getElementById('link-win').href = winUrl;
149− if (linuxUrl) document.getElementById('link-linux').href = linuxUrl;
150− // If no asset found for user's OS, fall back to releases page
151− if (!urls[userOS]) {
152− document.getElementById('primary-btn').href = `https://github.com/${REPO}/releases/latest`;
153− }
154− } catch {
155− document.getElementById('primary-btn').href = `https://github.com/${REPO}/releases/latest`;
156− }
157− }
158−
159− function copyCommand(cmd) {
160− const btn = event.currentTarget;
161− navigator.clipboard.writeText(cmd);
162− btn.querySelector('.copy-icon').classList.add('hidden');
163− btn.querySelector('.check-icon').classList.remove('hidden');
164− setTimeout(() => {
165− btn.querySelector('.copy-icon').classList.remove('hidden');
166− btn.querySelector('.check-icon').classList.add('hidden');
167− }, 1500);
168− }
169−
170− setup();
171− </script>
172−</Layout>
apps/marketing/src/styles/global.css+109−138
@@ -1,45 +1,25 @@
11 @import "tailwindcss";
22
33 @source "../**/*.astro";
4+@source "../**/*.tsx";
45
56 @theme {
6− --font-sans: "Manrope", ui-sans-serif, system-ui, sans-serif;
7− --font-display: "Instrument Serif", Georgia, serif;
7+ --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
8+ --font-display: "Geist", ui-sans-serif, system-ui, sans-serif;
9+ --font-serif: "Instrument Serif", ui-serif, Georgia, serif;
810 --font-mono: "JetBrains Mono", ui-monospace, monospace;
911
10− --color-surface: #0c0f0e;
11− --color-surface-raised: #141917;
12− --color-surface-overlay: #1a1f1d;
12+ /* Clean minimal palette */
13+ --color-surface: #fafaf7;
14+ --color-surface-2: #f3f3ee;
15+ --color-ink: #0d0d10;
16+ --color-ink-2: #4a4a4e;
17+ --color-ink-3: #8a8a8e;
18+ --color-rule: rgba(13, 13, 16, 0.07);
19+ --color-rule-strong: rgba(13, 13, 16, 0.14);
1320
14− --color-ink: #e4e8e6;
15− --color-ink-muted: #8a9490;
16− --color-ink-faint: #4a5552;
17−
18− --color-teal: #3dd8b5;
19− --color-teal-dim: #2aaa8e;
20− --color-teal-faint: rgba(61, 216, 181, 0.08);
21− --color-teal-glow: rgba(61, 216, 181, 0.15);
22−
23− --color-rule: rgba(255, 255, 255, 0.06);
24−}
25−
26−@media (prefers-color-scheme: light) {
27− :root {
28− --color-surface: #fafbfb;
29− --color-surface-raised: #f0f2f1;
30− --color-surface-overlay: #e5e8e7;
31−
32− --color-ink: #1a1f1d;
33− --color-ink-muted: #5a6560;
34− --color-ink-faint: #9ca5a0;
35−
36− --color-teal: #0d9b7a;
37− --color-teal-dim: #0a8068;
38− --color-teal-faint: rgba(13, 155, 122, 0.06);
39− --color-teal-glow: rgba(13, 155, 122, 0.1);
40−
41− --color-rule: rgba(0, 0, 0, 0.08);
42− }
21+ --color-accent: #635bff;
22+ --color-accent-hover: #5048e6;
4323 }
4424
4525 html {
@@ -47,160 +27,161 @@ html { · 1 unmodified line
4727 }
4828
4929 body {
50− background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
51− background-repeat: repeat;
52− background-size: 256px 256px;
53− color-scheme: light dark;
30+ font-family: var(--font-sans);
31+ color: var(--color-ink);
32+ background: var(--color-surface);
33+ letter-spacing: -0.011em;
5434 }
5535
56−@media (prefers-color-scheme: light) {
57− body {
58− background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.015'/%3E%3C/svg%3E");
59− }
36+::selection {
37+ background: var(--color-ink);
38+ color: var(--color-surface);
6039 }
6140
62−/* Staggered fade-in for page load */
41+/* ─── Faint hero background: off-white surface ─── */
42+.bg-soft {
43+ position: relative;
44+ background-color: var(--color-surface);
45+ isolation: isolate;
46+}
47+/* Pattern utility: mask the SVG, tint via background color */
48+.pattern-bg {
49+ position: absolute;
50+ inset: 0;
51+ background: var(--color-ink);
52+ -webkit-mask-repeat: repeat;
53+ mask-repeat: repeat;
54+ pointer-events: none;
55+ z-index: 1;
56+}
57+.pattern-graph {
58+ -webkit-mask-image: url("/pattern-graph-paper.svg");
59+ mask-image: url("/pattern-graph-paper.svg");
60+ -webkit-mask-size: 100px 100px;
61+ mask-size: 100px 100px;
62+ opacity: 0.08;
63+}
64+
65+/* Soft fade overlay — paint the surface color back over the pattern's bottom */
66+.pattern-fade-overlay {
67+ position: absolute;
68+ inset: 0;
69+ pointer-events: none;
70+ background: linear-gradient(180deg, transparent 0%, transparent 50%, var(--color-surface) 100%);
71+ z-index: 0;
72+}
73+
74+/* Italic serif accent — pairs with sharp Geist display */
75+.serif-italic {
76+ font-family: var(--font-serif);
77+ font-style: italic;
78+ font-weight: 400;
79+ letter-spacing: -0.01em;
80+ color: var(--color-accent);
81+}
82+
83+/* Page-load reveals */
6384 @keyframes rise {
6485 from {
6586 opacity: 0;
66− transform: translateY(24px);
87+ transform: translateY(10px);
6788 }
6889 to {
6990 opacity: 1;
7091 transform: translateY(0);
7192 }
7293 }
73−
7494 .rise {
75− animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
95+ animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
7696 }
77−.rise-d1 {
78− animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.08s both;
97+.rise-1 {
98+ animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both;
7999 }
80−.rise-d2 {
81− animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.16s both;
100+.rise-2 {
101+ animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.13s both;
82102 }
83−.rise-d3 {
84− animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.24s both;
103+.rise-3 {
104+ animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.21s both;
85105 }
86−.rise-d4 {
87− animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.32s both;
106+.rise-4 {
107+ animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
88108 }
89109
90−/* Typing cursor blink for code block */
91−@keyframes blink {
92− 0%,
93− 100% {
94− opacity: 1;
95− }
96− 50% {
97− opacity: 0;
98− }
110+/* Hairline-bordered surface for the beam panel — minimal elevation */
111+.surface-card {
112+ background: white;
113+ border: 1px solid var(--color-rule);
114+ border-radius: 14px;
115+ box-shadow:
116+ 0 1px 2px rgba(13, 13, 16, 0.03),
117+ 0 8px 24px -16px rgba(13, 13, 16, 0.08);
99118 }
100119
101−.cursor-blink {
102− animation: blink 1s step-end infinite;
120+/* Buttons */
121+.btn-primary {
122+ background: var(--color-ink);
123+ color: var(--color-surface);
124+ border-radius: 8px;
125+ padding: 0.65rem 1.1rem;
126+ font-weight: 500;
127+ font-size: 0.9rem;
128+ letter-spacing: -0.005em;
129+ transition: background 0.15s ease;
103130 }
104−
105−/* Smooth scroll-triggered reveals via IntersectionObserver */
106−.reveal {
107− opacity: 0;
108− transform: translateY(20px);
109− transition:
110− opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
111− transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
112−}
113−.reveal.visible {
114− opacity: 1;
115− transform: translateY(0);
131+.btn-primary:hover {
132+ background: #1f1f24;
116133 }
117134
118−/* Thin scrollbar for catalog */
119−.scrollbar-thin {
120− scrollbar-width: thin;
121− scrollbar-color: rgba(255, 255, 255, 0.08) transparent;
122−}
123−.scrollbar-thin::-webkit-scrollbar {
124− width: 4px;
125−}
126−.scrollbar-thin::-webkit-scrollbar-track {
127− background: transparent;
128−}
129−.scrollbar-thin::-webkit-scrollbar-thumb {
130− background: rgba(255, 255, 255, 0.1);
131− border-radius: 2px;
135+.btn-link {
136+ color: var(--color-ink-2);
137+ font-weight: 500;
138+ font-size: 0.9rem;
139+ letter-spacing: -0.005em;
140+ display: inline-flex;
141+ align-items: center;
142+ gap: 0.4rem;
143+ transition: color 0.15s ease;
132144 }
133−
134−@media (prefers-color-scheme: light) {
135− .scrollbar-thin {
136− scrollbar-color: rgba(0, 0, 0, 0.1) transparent;
137− }
138− .scrollbar-thin::-webkit-scrollbar-thumb {
139− background: rgba(0, 0, 0, 0.12);
140− }
145+.btn-link:hover {
146+ color: var(--color-ink);
141147 }
142148
149+/* Legal pages */
143150 .legal-prose {
144151 color: color-mix(in srgb, var(--color-ink) 85%, transparent);
145152 line-height: 1.75;
146153 font-size: 15.5px;
147154 }
148−
149155 .legal-prose > :first-child {
150156 margin-top: 0;
151157 }
152−
153−.legal-prose > :first-child::first-letter {
154− font-family: var(--font-display);
155− font-size: 3.4em;
156− float: left;
157− line-height: 0.85;
158− padding: 0.32rem 0.6rem 0 0;
159− color: var(--color-teal);
160−}
161−
162158 .legal-prose h2 {
163− position: relative;
164159 margin-top: 3rem;
165160 margin-bottom: 0.85rem;
166161 padding-top: 1.6rem;
167− color: var(--color-ink);
168162 font-family: var(--font-sans);
169163 font-size: 1.05rem;
170164 font-weight: 600;
171− line-height: 1.4;
172− letter-spacing: -0.005em;
173165 border-top: 1px solid var(--color-rule);
174166 }
175−
176−.legal-prose > h2:first-of-type {
177− margin-top: 2.4rem;
178−}
179−
180167 .legal-prose h3 {
181168 margin-top: 1.6rem;
182169 margin-bottom: 0.5rem;
183− color: var(--color-ink);
184170 font-size: 0.95rem;
185171 font-weight: 600;
186172 }
187−
188173 .legal-prose p,
189174 .legal-prose ul {
190175 margin: 0.85rem 0;
191176 }
192−
193177 .legal-prose ul {
194178 list-style: none;
195179 padding-left: 0;
196180 }
197−
198181 .legal-prose li {
199182 position: relative;
200− margin: 0.5rem 0;
201183 padding-left: 1.25rem;
202184 }
203−
204185 .legal-prose li::before {
205186 content: "";
206187 position: absolute;
@@ -209,25 +190,15 @@ body { · 2 unmodified lines
209190 width: 5px;
210191 height: 5px;
211192 border-radius: 50%;
212− background: var(--color-teal);
193+ background: var(--color-accent);
213194 opacity: 0.7;
214195 }
215−
216196 .legal-prose a {
217− color: var(--color-teal);
197+ color: var(--color-accent);
218198 text-decoration: underline;
219− text-decoration-color: color-mix(in srgb, var(--color-teal) 35%, transparent);
199+ text-decoration-color: color-mix(in srgb, var(--color-accent) 35%, transparent);
220200 text-underline-offset: 3px;
221− transition:
222− color 0.15s ease,
223− text-decoration-color 0.15s ease;
224201 }
225−
226−.legal-prose a:hover {
227− color: var(--color-teal-dim);
228− text-decoration-color: var(--color-teal-dim);
229−}
230−
231202 .legal-prose strong {
232203 color: var(--color-ink);
233204 font-weight: 600;
bun.lock+35−0
@@ -178,18 +178,26 @@ · 177 unmodified lines
178178 "version": "0.0.5",
179179 "dependencies": {
180180 "@astrojs/cloudflare": "^13.0.0",
181+ "@astrojs/react": "^5.0.4",
181182 "@executor-js/plugin-google-discovery": "workspace:*",
182183 "@executor-js/plugin-graphql": "workspace:*",
183184 "@executor-js/plugin-openapi": "workspace:*",
184185 "@executor-js/sdk": "workspace:*",
185186 "@tailwindcss/vite": "^4.2.2",
186187 "astro": "^6.1.3",
188+ "clsx": "^2.1.1",
187189 "effect": "catalog:",
190+ "motion": "^12.38.0",
188191 "posthog-js": "^1.372.5",
192+ "react": "^19.2.5",
193+ "react-dom": "^19.2.5",
194+ "tailwind-merge": "^3.5.0",
189195 "tailwindcss": "^4.2.2",
190196 },
191197 "devDependencies": {
192198 "@rhyssul/portless": "^0.13.0",
199+ "@types/react": "^19.2.14",
200+ "@types/react-dom": "^19.2.3",
193201 "wrangler": "^4.0.0",
194202 },
195203 },
@@ -312,6 +320,7 @@ · 116 unmodified lines
312320 },
313321 "devDependencies": {
314322 "@effect/atom-react": "catalog:",
323+ "@effect/platform-node": "catalog:",
315324 "@effect/vitest": "catalog:",
316325 "@types/node": "catalog:",
317326 "@types/react": "catalog:",
@@ -322,10 +331,12 @@ · 4 unmodified lines
322331 },
323332 "peerDependencies": {
324333 "@effect/atom-react": "catalog:",
334+ "@effect/platform-node": "catalog:",
325335 "react": "catalog:",
326336 },
327337 "optionalPeers": [
328338 "@effect/atom-react",
339+ "@effect/platform-node",
329340 "react",
330341 ],
331342 },
@@ -910,6 +921,8 @@ · 578 unmodified lines
910921
911922 "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],
912923
924+ "@astrojs/react": ["@astrojs/react@5.0.4", "", { "dependencies": { "@astrojs/internal-helpers": "0.9.0", "@vitejs/plugin-react": "^5.2.0", "devalue": "^5.6.4", "ultrahtml": "^1.6.0", "vite": "^7.3.2" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-yDNE4VnKOzCjH9dCBi7pT4F6kpI3M9TkS+uxnCB0sGIS6t5vKonOY+Hs/UUnSajJGT5jeBRfpI9IQp+r/n1fBA=="],
925+
913926 "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="],
914927
915928 "@astrojs/underscore-redirects": ["@astrojs/underscore-redirects@1.0.3", "", {}, "sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg=="],
@@ -984,6 +997,10 @@ · 68 unmodified lines
984997
985998 "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
986999
1000+ "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
1001+
1002+ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
1003+
9871004 "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
9881005
9891006 "@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.29.2", "", { "dependencies": { "core-js-pure": "^3.48.0" } }, "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw=="],
@@ -2194,6 +2211,14 @@ · 1204 unmodified lines
21942211
21952212 "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
21962213
2214+ "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
2215+
2216+ "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
2217+
2218+ "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
2219+
2220+ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
2221+
21972222 "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
21982223
21992224 "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
@@ -3998,6 +4023,8 @@ · 1798 unmodified lines
39984023
39994024 "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
40004025
4026+ "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
4027+
40014028 "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
40024029
40034030 "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
@@ -4602,6 +4629,12 @@ · 598 unmodified lines
46024629
46034630 "@astrojs/cloudflare/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
46044631
4632+ "@astrojs/react/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.0", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg=="],
4633+
4634+ "@astrojs/react/@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="],
4635+
4636+ "@astrojs/react/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
4637+
46054638 "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
46064639
46074640 "@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
@@ -5168,6 +5201,8 @@ · 560 unmodified lines
51685201
51695202 "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
51705203
5204+ "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
5205+
51715206 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
51725207
51735208 "@cloudflare/vite-plugin/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],