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
2import { defineConfig } from "astro/config";2import { defineConfig } from "astro/config";
3 3
4import tailwindcss from "@tailwindcss/vite";4import tailwindcss from "@tailwindcss/vite";
5import react from "@astrojs/react";
5 6
6import cloudflare from "@astrojs/cloudflare";7import cloudflare from "@astrojs/cloudflare";
7 8
8// https://astro.build/config9// https://astro.build/config
9export default defineConfig({10export default defineConfig({
10 output: "server",11 output: "server",
12 integrations: [react()],
11 vite: {13 vite: {
12 plugins: [tailwindcss()],14 plugins: [tailwindcss()],
13 },15 },
apps/marketing/package.json+8−0
@@ -15,18 +15,26 @@ · 14 unmodified lines
15 },15 },
16 "dependencies": {16 "dependencies": {
17 "@astrojs/cloudflare": "^13.0.0",17 "@astrojs/cloudflare": "^13.0.0",
18 "@astrojs/react": "^5.0.4",
18 "@executor-js/plugin-google-discovery": "workspace:*",19 "@executor-js/plugin-google-discovery": "workspace:*",
19 "@executor-js/plugin-graphql": "workspace:*",20 "@executor-js/plugin-graphql": "workspace:*",
20 "@executor-js/plugin-openapi": "workspace:*",21 "@executor-js/plugin-openapi": "workspace:*",
21 "@executor-js/sdk": "workspace:*",22 "@executor-js/sdk": "workspace:*",
22 "@tailwindcss/vite": "^4.2.2",23 "@tailwindcss/vite": "^4.2.2",
23 "astro": "^6.1.3",24 "astro": "^6.1.3",
25 "clsx": "^2.1.1",
24 "effect": "catalog:",26 "effect": "catalog:",
27 "motion": "^12.38.0",
25 "posthog-js": "^1.372.5",28 "posthog-js": "^1.372.5",
29 "react": "^19.2.5",
30 "react-dom": "^19.2.5",
31 "tailwind-merge": "^3.5.0",
26 "tailwindcss": "^4.2.2"32 "tailwindcss": "^4.2.2"
27 },33 },
28 "devDependencies": {34 "devDependencies": {
29 "@rhyssul/portless": "^0.13.0",35 "@rhyssul/portless": "^0.13.0",
36 "@types/react": "^19.2.14",
37 "@types/react-dom": "^19.2.3",
30 "wrangler": "^4.0.0"38 "wrangler": "^4.0.0"
31 },39 },
32 "engines": {40 "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
17 executor17 executor
18 </a>18 </a>
19 <div class="flex items-center gap-5 sm:gap-8">19 <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>
23 <a href="/privacy" class="text-[13px] text-ink-muted transition-colors hover:text-ink">20 <a href="/privacy" class="text-[13px] text-ink-muted transition-colors hover:text-ink">
24 Privacy21 Privacy
25 </a>22 </a>
apps/marketing/src/components/animated-beam-demo.tsx+371−0
@@ -0,0 +1,371 @@
1"use client";
2
3import React, { forwardRef, useRef } from "react";
4
5import { cn } from "../lib/utils";
6import { AnimatedBeam } from "./ui/animated-beam";
7
8type Variant = "blueprint" | "brutalist" | "pastel" | "cyber" | "editorial" | "stripe";
9
10const 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
106const 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});
129Node.displayName = "Node";
130
131export 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
308function 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
340const 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
3import { useEffect, useId, useState, type RefObject } from "react";
4import { motion } from "motion/react";
5
6import { cn } from "../../lib/utils";
7
8export 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
30export 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
7}7}
8 8
9const {9const {
10 title = 'Executor — The Integration Layer for AI Agents',10 title = 'Executor — The gateway to connect your agent to everything',
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.'11 description = 'One place every agent plugs into every tool you already use. Wire it up once, bring the whole team.'
12} = Astro.props12} = Astro.props
13---13---
14 14
@@ -20,48 +20,16 @@ const { · 5 unmodified lines
20 <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" />20 <link rel="icon" type="image/png" sizes="192x192" href="/favicon-192.png" />
21 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />21 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
22 <meta name="viewport" content="width=device-width, initial-scale=1" />22 <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" />
24 <meta name="description" content={description} />24 <meta name="description" content={description} />
25 <meta property="og:title" content={title} />25 <meta property="og:title" content={title} />
26 <meta property="og:description" content={description} />26 <meta property="og:description" content={description} />
27 <title>{title}</title>27 <title>{title}</title>
28 <link rel="preconnect" href="https://fonts.googleapis.com" />28 <link rel="preconnect" href="https://fonts.googleapis.com" />
29 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />29 <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" />
31 </head>31 </head>
32 <body class="bg-surface text-ink antialiased selection:bg-teal/20 selection:text-teal">32 <body class="antialiased">
33 <slot />33 <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>
66 </body>34 </body>
67</html>35</html>
apps/marketing/src/lib/utils.ts+6−0
@@ -0,0 +1,6 @@
1import { type ClassValue, clsx } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4export function cn(...inputs: ClassValue[]) {
5 return twMerge(clsx(inputs));
6}
apps/marketing/src/pages/index.astro+236−914
@@ -1,971 +1,293 @@
1---1---
2export const prerender = true
3import Layout from '../layouts/Layout.astro'2import Layout from '../layouts/Layout.astro'
3import { AnimatedBeamDemo } from '../components/animated-beam-demo'
4---4---
5 5
6<Layout>6<Layout>
7 <!-- ─── NAV ─── -->7 <main class="bg-soft min-h-dvh relative antialiased">
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>
20 8
21 <!-- ─── HERO ─── -->9 {/* Pattern + fade — span the top 100vh, behind everything */}
22 <section class="relative min-h-screen flex flex-col justify-center pt-14 overflow-hidden">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>
23 <div class="absolute top-[15%] right-[10%] w-[500px] h-[500px] bg-teal/[0.04] rounded-full blur-[150px] 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>
24 12
25 <div class="max-w-[1200px] mx-auto px-6 sm:px-10 w-full">13 <div class="relative z-10">
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>
31 14
32 <p class="rise-d2 text-[17px] leading-[1.7] text-ink-muted mb-10">15 {/* NAV */}
33 One catalog. Every agent. Shared auth, policies, and tools across16 <nav>
34 Cursor, Claude Code, OpenCode&mdash;or your own.17 <div class="max-w-[1200px] mx-auto px-6 sm:px-10 h-14 flex items-center justify-between">
35 </p>18 <a href="/" class="flex items-center gap-2.5">
36 19 <img src="/favicon-192.png" alt="" class="w-5 h-5" />
37 <div class="rise-d3 flex flex-wrap items-center justify-center gap-3">20 <span class="text-[15px] font-semibold tracking-[-0.01em] text-ink">executor</span>
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">21 </a>
39 Try Cloud22 <div class="flex items-center gap-5">
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>23 <a href="https://github.com/RhysSullivan/executor" class="text-[14px] font-medium text-ink-2 hover:text-ink transition-colors">GitHub</a>
41 </a>24 <a href="/cloud" class="btn-primary">Try Cloud →</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>
45 </div>25 </div>
46 </div>26 </div>
47 27 </nav>
48 <!-- URL input bar -->28
49 <div class="rise-d4 mb-4" id="detect-bar">29 {/* HERO — centered, tight */}
50 <div class="relative">30 <section 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">31 <div class="relative z-10 px-6 sm:px-10 pt-28 pb-32 sm:pt-36 sm:pb-44">
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>32 <div class="max-w-[920px] mx-auto text-center">
53 <input33 <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">
54 id="detect-input"34 Connect any agent to <span class="serif-italic">everything</span>.
55 type="url"35 </h1>
56 placeholder="Paste any API URL to detect tools..."36 <div class="rise-2 max-w-[760px] mx-auto mb-12">
57 class="flex-1 bg-transparent text-[14px] text-ink placeholder:text-ink-faint outline-none font-mono"37 <div class="surface-card p-6 sm:p-7">
58 autocomplete="off"38 <AnimatedBeamDemo client:load variant="stripe" />
59 spellcheck="false"39 </div>
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>
64 </div>40 </div>
65 <div id="detect-status" class="hidden absolute left-0 right-0 -bottom-7 text-[11px] font-mono text-center"></div>41 <p class="rise-3 text-[18px] leading-[1.55] text-ink-2 mx-auto max-w-[44ch] mb-9 text-pretty">
66 <div id="detect-examples" class="mt-3 flex flex-wrap items-center gap-2 justify-center">42 One gateway. Every tool. Every team.
67 <span class="text-[11px] text-ink-faint">Try:</span>43 </p>
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>44 <div class="rise-4 flex flex-wrap items-center justify-center gap-5">
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>45 <a href="/cloud" class="btn-primary">Try Cloud →</a>
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>46 <a href="#install" class="btn-link">
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>47 <span class="font-mono text-[13px] text-ink-3">$</span>
48 <span>npx executor web</span>
49 </a>
72 </div>50 </div>
73 </div>51 </div>
74 </div>52 </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>
75 67
76 <!-- Full-width interactive catalog UI -->68 <div class="grid sm:grid-cols-2 gap-5">
77 <div class="rise-d4 relative" id="hero-catalog">69 {/* Every tool */}
78 <div class="absolute -inset-px rounded-xl bg-gradient-to-b from-teal/10 via-transparent to-transparent pointer-events-none"></div>70 <div class="surface-card p-7 sm:p-8">
79 <div class="relative bg-surface-raised border border-rule rounded-xl overflow-hidden text-[13px]">71 <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
80 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)">
81 <!-- Top bar -->73 <rect x="3" y="3" width="7" height="7" rx="1.5"/>
82 <div class="flex items-center justify-between px-5 py-3 border-b border-rule">74 <rect x="14" y="3" width="7" height="7" rx="1.5"/>
83 <div class="flex items-center gap-3">75 <rect x="3" y="14" width="7" height="7" rx="1.5"/>
84 <span class="font-display italic text-ink text-sm">executor</span>76 <rect x="14" y="14" width="7" height="7" rx="1.5"/>
85 <span class="text-ink-faint">/</span>77 </svg>
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>
92 </div>78 </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>
93 </div>83 </div>
94 84
95 <!-- Content: sidebar + main -->85 {/* Every agent */}
96 <div class="flex">86 <div class="surface-card p-7 sm:p-8">
97 87 <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
98 <!-- Sidebar: sources -->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)">
99 <div class="w-[200px] shrink-0 border-r border-rule p-3 space-y-1 hidden sm:block">89 <circle cx="9" cy="8" r="3"/>
100 <div class="text-[10px] font-mono text-ink-faint uppercase tracking-[0.1em] mb-2.5 px-2">Connected</div>90 <circle cx="17" cy="9" r="2.5"/>
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">91 <path d="M3 19c1-3.5 4-5 6-5s5 1.5 6 5"/>
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>92 <path d="M14 19c.5-2 2.5-3 3-3s2.5 1 3 3"/>
103 <div class="flex-1 min-w-0">93 </svg>
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>
149 </div>94 </div>
150 95 <h3 class="text-[18px] font-semibold tracking-[-0.01em] text-ink mb-2">Every agent</h3>
151 <!-- Main: tool tree -->96 <p class="text-[14.5px] leading-[1.55] text-ink-2 text-pretty">
152 <div class="flex-1 min-w-0 flex flex-col">97 Claude Code, Cursor, Codex, OpenCode, Windsurf, or your own. One catalog, no per-agent setup.
153 <!-- Search -->98 </p>
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
170 </div>99 </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');
407 100
408 // ── Live detection ──101 {/* Permission model */}
409 const detectInput = document.getElementById('detect-input');102 <div class="surface-card p-7 sm:p-8">
410 const detectBtn = document.getElementById('detect-btn');103 <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
411 const detectStatus = document.getElementById('detect-status');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)">
412 const detectExamples = document.getElementById('detect-examples');105 <path d="M12 3l8 3v6c0 4.5-3.4 8.4-8 9-4.6-.6-8-4.5-8-9V6l8-3z"/>
413 106 <path d="M9 12l2 2 4-4"/>
414 const kindColors = {107 </svg>
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>
560 </div>108 </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>
561 </div>113 </div>
562 114
563 <p class="text-[13px] text-ink-faint mt-auto">Every schema dumped into context. Tokens wasted. Poor performance.</p>115 {/* Auth */}
564 </div>116 <div class="surface-card p-7 sm:p-8">
565 117 <div class="size-9 rounded-md flex items-center justify-center mb-5" style="background: rgba(99,91,255,0.08)">
566 <!-- Era 2: Bash -->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)">
567 <div class="rounded-xl border border-rule bg-surface-raised p-6 sm:p-8 flex flex-col">119 <circle cx="8" cy="14" r="4"/>
568 <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em] mb-6">Era 2 &middot; Bash</span>120 <path d="M11 11l9-9"/>
569 121 <path d="M16 6l3 3"/>
570 <div class="flex-1 flex flex-col items-center gap-3 my-6">122 <path d="M14 8l3 3"/>
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>123 </svg>
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>
585 </div>124 </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>
586 </div>129 </div>
587 130 </div>
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>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>
589 </div>142 </div>
590 143
591 <!-- Era 3: Executor -->144 <div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-x-10 gap-y-12">
592 <div class="rounded-xl border border-teal/20 bg-surface-raised p-6 sm:p-8 flex flex-col relative overflow-hidden">145 <div>
593 <div class="absolute inset-0 bg-gradient-to-b from-teal/[0.03] to-transparent pointer-events-none"></div>146 <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">01</span>
594 <span class="relative font-mono text-[11px] text-teal uppercase tracking-[0.15em] mb-6">Era 3 &middot; Executor</span>147 <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One catalog</h3>
595 148 <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
596 <div class="relative flex-1 flex flex-col items-center gap-3 my-6">149 Every integration indexed once. Searchable, typed, discoverable from any agent.
597 <!-- Multiple agents -->150 </p>
598 <div class="w-full grid grid-cols-3 gap-1.5">151 </div>
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>152 <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>153 <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">02</span>
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>154 <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One auth layer</h3>
602 </div>155 <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
603 <svg class="w-full h-8 text-teal/50" viewBox="0 0 200 32" fill="none" stroke="currentColor" stroke-width="1.5">156 Credentials live with Executor. Never pasted into a prompt or copied between machines.
604 <line x1="33" y1="0" x2="100" y2="32" />157 </p>
605 <line x1="100" y1="0" x2="100" y2="32" />158 </div>
606 <line x1="167" y1="0" x2="100" y2="32" />159 <div>
607 </svg>160 <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">03</span>
608 <!-- Executor box with features -->161 <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One policy set</h3>
609 <div class="w-full rounded-lg border border-teal/30 bg-teal/[0.06] px-4 py-3">162 <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
610 <div class="text-center text-[13px] font-mono text-teal mb-2">executor</div>163 Same rules across every agent and every machine. Wildcard, version, and team scoped.
611 <div class="flex justify-center gap-2">164 </p>
612 <span class="text-[9px] font-mono text-teal/60 border border-teal/15 rounded px-1.5 py-0.5">auth</span>165 </div>
613 <span class="text-[9px] font-mono text-teal/60 border border-teal/15 rounded px-1.5 py-0.5">policies</span>166 <div>
614 <span class="text-[9px] font-mono text-teal/60 border border-teal/15 rounded px-1.5 py-0.5">catalog</span>167 <span class="font-mono text-[12px] tracking-[0.1em]" style="color: var(--color-accent)">04</span>
615 </div>168 <h3 class="text-[16px] font-semibold tracking-[-0.005em] text-ink mt-3 mb-2">One audit log</h3>
616 </div>169 <p class="text-[14px] leading-[1.6] text-ink-2 text-pretty">
617 <svg class="w-full h-8 text-teal/50" viewBox="0 0 200 32" fill="none" stroke="currentColor" stroke-width="1.5">170 Every tool call, approval, and block — one place to query, one source of truth.
618 <line x1="50" y1="0" x2="20" y2="32" />171 </p>
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>
631 </div>172 </div>
632
633 <p class="relative text-[13px] text-teal/70 mt-auto">Typed, sandboxed, shared across all agents.</p>
634 </div>173 </div>
635 </div>174 </div>
636 </div>175 </section>
637 </section>176
638 177 {/* ─── ROADMAP ─── */}
639 <!-- ─── WHAT YOU GET: icon grid ─── -->178 <section id="roadmap" class="py-24 sm:py-32 border-t border-rule">
640 <section class="py-32 sm:py-40 relative">179 <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
641 <div class="absolute inset-0 border-t border-b border-rule"></div>180 <div class="mb-16 max-w-2xl">
642 181 <div class="font-mono text-[12px] uppercase tracking-[0.18em] text-ink-3 mb-4">Roadmap</div>
643 <div class="relative max-w-[1200px] mx-auto px-6 sm:px-10">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">
644 <div class="reveal mb-16">183 Where it's <span class="serif-italic">going</span>.
645 <span class="text-[11px] font-mono uppercase tracking-[0.2em] text-ink-faint mb-4 block">The stack</span>184 </h2>
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>
666 </div>185 </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>
696 186
697 <div class="reveal grid md:grid-cols-3 gap-6">187 <div class="grid md:grid-cols-3 gap-6">
698 <!-- Now -->188 {/* Now */}
699 <div class="rounded-xl border border-teal/20 p-6 sm:p-8 relative overflow-hidden">189 <div class="surface-card p-7" style="border-color: rgba(99,91,255,0.25)">
700 <div class="absolute inset-0 bg-gradient-to-b from-teal/[0.03] to-transparent pointer-events-none"></div>
701 <div class="relative">
702 <div class="flex items-center gap-2 mb-5">190 <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>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>
704 <span class="font-mono text-[11px] text-teal uppercase tracking-[0.15em]">Now</span>192 <span class="font-mono text-[11px] uppercase tracking-[0.18em]" style="color: var(--color-accent)">Now</span>
705 </div>193 </div>
706 <ul class="space-y-3 text-[14px]">194 <ul role="list" class="space-y-2.5 text-[14px] text-ink-2">
707 <li class="flex items-start gap-2.5">195 <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Core SDK &amp; CLI</span></li>
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>196 <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>MCP bridge</span></li>
709 <span class="text-ink-muted">Core SDK &amp; CLI</span>197 <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Permission engine</span></li>
710 </li>198 <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Local web UI</span></li>
711 <li class="flex items-start gap-2.5">199 <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Desktop app</span></li>
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>200 <li class="flex gap-2.5"><span class="text-ink-3">→</span><span>Cloud &amp; team management</span></li>
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>
731 </ul>201 </ul>
732 </div>202 </div>
733 </div>
734 203
735 <!-- Soon -->204 {/* Soon */}
736 <div class="rounded-xl border border-rule p-6 sm:p-8">205 <div class="surface-card p-7">
737 <div class="flex items-center gap-2 mb-5">206 <div class="flex items-center gap-2 mb-5">
738 <div class="w-2 h-2 rounded-full bg-ink-faint"></div>207 <span class="size-1.5 rounded-full bg-ink-3"></span>
739 <span class="font-mono text-[11px] text-ink-muted uppercase tracking-[0.15em]">Soon</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>
740 </div>216 </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>
748 217
749 <!-- Later -->218 {/* Later */}
750 <div class="rounded-xl border border-rule/50 p-6 sm:p-8">219 <div class="surface-card p-7" style="opacity: 0.85">
751 <div class="flex items-center gap-2 mb-5">220 <div class="flex items-center gap-2 mb-5">
752 <div class="w-2 h-2 rounded-full bg-ink-faint/40"></div>221 <span class="size-1.5 rounded-full" style="background: rgba(13,13,16,0.25)"></span>
753 <span class="font-mono text-[11px] text-ink-faint uppercase tracking-[0.15em]">Later</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>
754 </div>230 </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>
761 </div>231 </div>
762 </div>232 </div>
763 </div>233 </section>
764 </section>234
765 235 {/* ─── GET STARTED ─── */}
766 <!-- ─── GET STARTED: dual path ─── -->236 <section id="install" class="py-24 sm:py-32 border-t border-rule">
767 <section id="get-started" class="py-32 sm:py-40 relative">237 <div class="max-w-[1200px] mx-auto px-6 sm:px-10">
768 <div class="absolute inset-0 border-t border-rule"></div>238 <div class="mb-14 mx-auto text-center max-w-2xl">
769 239 <div class="font-mono text-[12px] uppercase tracking-[0.18em] text-ink-3 mb-4">Get started</div>
770 <div class="relative max-w-[1200px] mx-auto px-6 sm:px-10">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">
771 <div class="reveal max-w-2xl mx-auto text-center mb-16">241 Pick your <span class="serif-italic">path</span>.
772 <span class="text-[11px] font-mono uppercase tracking-[0.2em] text-ink-faint mb-4 block">Get started</span>242 </h2>
773 <h2 class="font-display text-[clamp(2rem,5vw,4rem)] leading-[1.05] tracking-tight mb-5">243 </div>
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">
783 244
784 <!-- Cloud (recommended) -->245 <div class="grid md:grid-cols-2 gap-6 max-w-[920px] mx-auto">
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">246 {/* Cloud */}
786 <div class="absolute inset-0 bg-gradient-to-b from-teal/[0.04] to-transparent pointer-events-none"></div>247 <div class="surface-card p-8 flex flex-col" style="border-color: rgba(99,91,255,0.25)">
787 <div class="relative">248 <div class="flex items-center gap-2 mb-4">
788 <div class="flex items-center justify-between mb-5">249 <span class="font-mono text-[11px] uppercase tracking-[0.18em]" style="color: var(--color-accent)">Recommended</span>
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>
791 </div>250 </div>
792 251 <h3 class="text-[22px] font-semibold tracking-[-0.01em] text-ink mb-3">Cloud</h3>
793 <h3 class="font-display text-[clamp(1.5rem,2.6vw,2rem)] leading-[1.1] tracking-tight text-ink mb-3">252 <p class="text-[14.5px] leading-[1.55] text-ink-2 mb-6 text-pretty flex-1">
794 Run it with <span class="italic text-teal">your team</span>253 Hosted Executor. Auth, sync, policies, and the whole team online in five minutes. Free tier to start.
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.
798 </p>254 </p>
799 255 <a href="/cloud" class="btn-primary self-start">Try Cloud →</a>
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>
814 </div>256 </div>
815 257
816 <div class="relative">258 {/* Local */}
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">259 <div class="surface-card p-8 flex flex-col">
818 Try Cloud260 <div class="flex items-center gap-2 mb-4">
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>261 <span class="font-mono text-[11px] uppercase tracking-[0.18em] text-ink-3">Open source</span>
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>
830 </div>262 </div>
831 263 <h3 class="text-[22px] font-semibold tracking-[-0.01em] text-ink mb-3">Local</h3>
832 <h3 class="font-display text-[clamp(1.5rem,2.6vw,2rem)] leading-[1.1] tracking-tight text-ink mb-3">264 <p class="text-[14.5px] leading-[1.55] text-ink-2 mb-6 text-pretty flex-1">
833 Run it on <span class="italic text-ink-muted">your laptop</span>265 Run on your own machine. Keychain-backed secrets. Zero data leaves your laptop. MIT licensed.
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.
837 </p>266 </p>
838 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)">
839 <!-- MCP installation steps -->268 <span class="text-ink-3">$</span> npx executor web
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>
880 </div>269 </div>
881 </div>270 </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>
889 </div>271 </div>
890 </div>272 </div>
891 </div>273 </section>
892 </section>274
893 275 {/* ─── FOOTER ─── */}
894 <script>276 <footer class="border-t border-rule py-10">
895 // Package manager tabs277 <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">
896 const cmds: Record<string, string> = {278 <div class="flex items-center gap-2.5">
897 npm: 'npm install -g executor',279 <img src="/favicon-192.png" alt="" class="w-4 h-4 opacity-80" />
898 bun: 'bun add -g executor',280 <span class="text-[13px] font-medium text-ink-2">executor</span>
899 pnpm: 'pnpm add -g executor',281 <span class="text-[12px] text-ink-3 ml-2">© {new Date().getFullYear()}</span>
900 };282 </div>
901 const tabs = document.querySelectorAll<HTMLButtonElement>('.pm-tab');283 <div class="flex items-center gap-6 text-[13px] text-ink-2">
902 const cmdText = document.querySelector('.cmd-text');284 <a href="https://github.com/RhysSullivan/executor" class="hover:text-ink transition-colors">GitHub</a>
903 tabs.forEach(tab => {285 <a href="/privacy" class="hover:text-ink transition-colors">Privacy</a>
904 tab.addEventListener('click', () => {286 <a href="/terms" class="hover:text-ink transition-colors">Terms</a>
905 tabs.forEach(t => {287 </div>
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>
968 </div>288 </div>
289 </footer>
290
969 </div>291 </div>
970 </footer>292 </main>
971</Layout>293</Layout>
apps/marketing/src/pages/setup.astro+0−172
@@ -1,172 +0,0 @@
1---
2export const prerender = true
3import 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 @@
1@import "tailwindcss";1@import "tailwindcss";
2 2
3@source "../**/*.astro";3@source "../**/*.astro";
4@source "../**/*.tsx";
4 5
5@theme {6@theme {
6 --font-sans: "Manrope", ui-sans-serif, system-ui, sans-serif;7 --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
7 --font-display: "Instrument Serif", Georgia, serif;8 --font-display: "Geist", ui-sans-serif, system-ui, sans-serif;
9 --font-serif: "Instrument Serif", ui-serif, Georgia, serif;
8 --font-mono: "JetBrains Mono", ui-monospace, monospace;10 --font-mono: "JetBrains Mono", ui-monospace, monospace;
9 11
10 --color-surface: #0c0f0e;12 /* Clean minimal palette */
11 --color-surface-raised: #141917;13 --color-surface: #fafaf7;
12 --color-surface-overlay: #1a1f1d;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);
13 20
14 --color-ink: #e4e8e6;21 --color-accent: #635bff;
15 --color-ink-muted: #8a9490;22 --color-accent-hover: #5048e6;
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 }
43}23}
44 24
45html {25html {
@@ -47,160 +27,161 @@ html { · 1 unmodified line
47}27}
48 28
49body {29body {
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");30 font-family: var(--font-sans);
51 background-repeat: repeat;31 color: var(--color-ink);
52 background-size: 256px 256px;32 background: var(--color-surface);
53 color-scheme: light dark;33 letter-spacing: -0.011em;
54}34}
55 35
56@media (prefers-color-scheme: light) {36::selection {
57 body {37 background: var(--color-ink);
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");38 color: var(--color-surface);
59 }
60}39}
61 40
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 */
63@keyframes rise {84@keyframes rise {
64 from {85 from {
65 opacity: 0;86 opacity: 0;
66 transform: translateY(24px);87 transform: translateY(10px);
67 }88 }
68 to {89 to {
69 opacity: 1;90 opacity: 1;
70 transform: translateY(0);91 transform: translateY(0);
71 }92 }
72}93}
73
74.rise {94.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;
76}96}
77.rise-d1 {97.rise-1 {
78 animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.08s both;98 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both;
79}99}
80.rise-d2 {100.rise-2 {
81 animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.16s both;101 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.13s both;
82}102}
83.rise-d3 {103.rise-3 {
84 animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.24s both;104 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.21s both;
85}105}
86.rise-d4 {106.rise-4 {
87 animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.32s both;107 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
88}108}
89 109
90/* Typing cursor blink for code block */110/* Hairline-bordered surface for the beam panel — minimal elevation */
91@keyframes blink {111.surface-card {
92 0%,112 background: white;
93 100% {113 border: 1px solid var(--color-rule);
94 opacity: 1;114 border-radius: 14px;
95 }115 box-shadow:
96 50% {116 0 1px 2px rgba(13, 13, 16, 0.03),
97 opacity: 0;117 0 8px 24px -16px rgba(13, 13, 16, 0.08);
98 }
99}118}
100 119
101.cursor-blink {120/* Buttons */
102 animation: blink 1s step-end infinite;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;
103}130}
104 131.btn-primary:hover {
105/* Smooth scroll-triggered reveals via IntersectionObserver */132 background: #1f1f24;
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);
116}133}
117 134
118/* Thin scrollbar for catalog */135.btn-link {
119.scrollbar-thin {136 color: var(--color-ink-2);
120 scrollbar-width: thin;137 font-weight: 500;
121 scrollbar-color: rgba(255, 255, 255, 0.08) transparent;138 font-size: 0.9rem;
122}139 letter-spacing: -0.005em;
123.scrollbar-thin::-webkit-scrollbar {140 display: inline-flex;
124 width: 4px;141 align-items: center;
125}142 gap: 0.4rem;
126.scrollbar-thin::-webkit-scrollbar-track {143 transition: color 0.15s ease;
127 background: transparent;
128}
129.scrollbar-thin::-webkit-scrollbar-thumb {
130 background: rgba(255, 255, 255, 0.1);
131 border-radius: 2px;
132}144}
133 145.btn-link:hover {
134@media (prefers-color-scheme: light) {146 color: var(--color-ink);
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 }
141}147}
142 148
149/* Legal pages */
143.legal-prose {150.legal-prose {
144 color: color-mix(in srgb, var(--color-ink) 85%, transparent);151 color: color-mix(in srgb, var(--color-ink) 85%, transparent);
145 line-height: 1.75;152 line-height: 1.75;
146 font-size: 15.5px;153 font-size: 15.5px;
147}154}
148
149.legal-prose > :first-child {155.legal-prose > :first-child {
150 margin-top: 0;156 margin-top: 0;
151}157}
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
162.legal-prose h2 {158.legal-prose h2 {
163 position: relative;
164 margin-top: 3rem;159 margin-top: 3rem;
165 margin-bottom: 0.85rem;160 margin-bottom: 0.85rem;
166 padding-top: 1.6rem;161 padding-top: 1.6rem;
167 color: var(--color-ink);
168 font-family: var(--font-sans);162 font-family: var(--font-sans);
169 font-size: 1.05rem;163 font-size: 1.05rem;
170 font-weight: 600;164 font-weight: 600;
171 line-height: 1.4;
172 letter-spacing: -0.005em;
173 border-top: 1px solid var(--color-rule);165 border-top: 1px solid var(--color-rule);
174}166}
175
176.legal-prose > h2:first-of-type {
177 margin-top: 2.4rem;
178}
179
180.legal-prose h3 {167.legal-prose h3 {
181 margin-top: 1.6rem;168 margin-top: 1.6rem;
182 margin-bottom: 0.5rem;169 margin-bottom: 0.5rem;
183 color: var(--color-ink);
184 font-size: 0.95rem;170 font-size: 0.95rem;
185 font-weight: 600;171 font-weight: 600;
186}172}
187
188.legal-prose p,173.legal-prose p,
189.legal-prose ul {174.legal-prose ul {
190 margin: 0.85rem 0;175 margin: 0.85rem 0;
191}176}
192
193.legal-prose ul {177.legal-prose ul {
194 list-style: none;178 list-style: none;
195 padding-left: 0;179 padding-left: 0;
196}180}
197
198.legal-prose li {181.legal-prose li {
199 position: relative;182 position: relative;
200 margin: 0.5rem 0;
201 padding-left: 1.25rem;183 padding-left: 1.25rem;
202}184}
203
204.legal-prose li::before {185.legal-prose li::before {
205 content: "";186 content: "";
206 position: absolute;187 position: absolute;
@@ -209,25 +190,15 @@ body { · 2 unmodified lines
209 width: 5px;190 width: 5px;
210 height: 5px;191 height: 5px;
211 border-radius: 50%;192 border-radius: 50%;
212 background: var(--color-teal);193 background: var(--color-accent);
213 opacity: 0.7;194 opacity: 0.7;
214}195}
215
216.legal-prose a {196.legal-prose a {
217 color: var(--color-teal);197 color: var(--color-accent);
218 text-decoration: underline;198 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);
220 text-underline-offset: 3px;200 text-underline-offset: 3px;
221 transition:
222 color 0.15s ease,
223 text-decoration-color 0.15s ease;
224}201}
225
226.legal-prose a:hover {
227 color: var(--color-teal-dim);
228 text-decoration-color: var(--color-teal-dim);
229}
230
231.legal-prose strong {202.legal-prose strong {
232 color: var(--color-ink);203 color: var(--color-ink);
233 font-weight: 600;204 font-weight: 600;
bun.lock+35−0
@@ -178,18 +178,26 @@ · 177 unmodified lines
178 "version": "0.0.5",178 "version": "0.0.5",
179 "dependencies": {179 "dependencies": {
180 "@astrojs/cloudflare": "^13.0.0",180 "@astrojs/cloudflare": "^13.0.0",
181 "@astrojs/react": "^5.0.4",
181 "@executor-js/plugin-google-discovery": "workspace:*",182 "@executor-js/plugin-google-discovery": "workspace:*",
182 "@executor-js/plugin-graphql": "workspace:*",183 "@executor-js/plugin-graphql": "workspace:*",
183 "@executor-js/plugin-openapi": "workspace:*",184 "@executor-js/plugin-openapi": "workspace:*",
184 "@executor-js/sdk": "workspace:*",185 "@executor-js/sdk": "workspace:*",
185 "@tailwindcss/vite": "^4.2.2",186 "@tailwindcss/vite": "^4.2.2",
186 "astro": "^6.1.3",187 "astro": "^6.1.3",
188 "clsx": "^2.1.1",
187 "effect": "catalog:",189 "effect": "catalog:",
190 "motion": "^12.38.0",
188 "posthog-js": "^1.372.5",191 "posthog-js": "^1.372.5",
192 "react": "^19.2.5",
193 "react-dom": "^19.2.5",
194 "tailwind-merge": "^3.5.0",
189 "tailwindcss": "^4.2.2",195 "tailwindcss": "^4.2.2",
190 },196 },
191 "devDependencies": {197 "devDependencies": {
192 "@rhyssul/portless": "^0.13.0",198 "@rhyssul/portless": "^0.13.0",
199 "@types/react": "^19.2.14",
200 "@types/react-dom": "^19.2.3",
193 "wrangler": "^4.0.0",201 "wrangler": "^4.0.0",
194 },202 },
195 },203 },
@@ -312,6 +320,7 @@ · 116 unmodified lines
312 },320 },
313 "devDependencies": {321 "devDependencies": {
314 "@effect/atom-react": "catalog:",322 "@effect/atom-react": "catalog:",
323 "@effect/platform-node": "catalog:",
315 "@effect/vitest": "catalog:",324 "@effect/vitest": "catalog:",
316 "@types/node": "catalog:",325 "@types/node": "catalog:",
317 "@types/react": "catalog:",326 "@types/react": "catalog:",
@@ -322,10 +331,12 @@ · 4 unmodified lines
322 },331 },
323 "peerDependencies": {332 "peerDependencies": {
324 "@effect/atom-react": "catalog:",333 "@effect/atom-react": "catalog:",
334 "@effect/platform-node": "catalog:",
325 "react": "catalog:",335 "react": "catalog:",
326 },336 },
327 "optionalPeers": [337 "optionalPeers": [
328 "@effect/atom-react",338 "@effect/atom-react",
339 "@effect/platform-node",
329 "react",340 "react",
330 ],341 ],
331 },342 },
@@ -910,6 +921,8 @@ · 578 unmodified lines
910 921
911 "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],922 "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],
912 923
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
913 "@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=="],926 "@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=="],
914 927
915 "@astrojs/underscore-redirects": ["@astrojs/underscore-redirects@1.0.3", "", {}, "sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg=="],928 "@astrojs/underscore-redirects": ["@astrojs/underscore-redirects@1.0.3", "", {}, "sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg=="],
@@ -984,6 +997,10 @@ · 68 unmodified lines
984 997
985 "@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=="],998 "@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=="],
986 999
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
987 "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],1004 "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
988 1005
989 "@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.29.2", "", { "dependencies": { "core-js-pure": "^3.48.0" } }, "sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw=="],1006 "@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
2194 2211
2195 "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],2212 "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
2196 2213
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
2197 "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],2222 "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
2198 2223
2199 "@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=="],2224 "@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
3998 4023
3999 "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=="],4024 "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=="],
4000 4025
4026 "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
4027
4001 "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=="],4028 "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=="],
4002 4029
4003 "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=="],4030 "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
4602 4629
4603 "@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=="],4630 "@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=="],
4604 4631
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
4605 "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],4638 "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
4606 4639
4607 "@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=="],4640 "@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
5168 5201
5169 "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],5202 "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
5170 5203
5204 "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
5205
5171 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],5206 "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
5172 5207
5173 "@cloudflare/vite-plugin/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],5208 "@cloudflare/vite-plugin/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],