Unified · Split
| @@ -1,5 +1,5 @@ | |||
| 1 | --- | 1 | --- |
| 2 | import Layout from "./Layout.astro"; | 2 | import RailLayout from "./RailLayout.astro"; |
| 3 | 3 | ||
| 4 | interface Props { | 4 | interface Props { |
| 5 | title: string; | 5 | title: string; |
| @@ -11,17 +11,9 @@ interface Props { · 5 unmodified lines | |||
| 11 | authorAvatar: string; | 11 | authorAvatar: string; |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | const { | 14 | const { title, description, date, author, authorHandle, authorRole, authorAvatar } = |
| 15 | title, | 15 | Astro.props; |
| 16 | description, | ||
| 17 | date, | ||
| 18 | author, | ||
| 19 | authorHandle, | ||
| 20 | authorRole, | ||
| 21 | authorAvatar, | ||
| 22 | } = Astro.props; | ||
| 23 | 16 | ||
| 24 | const GH = "https://github.com/UsefulSoftwareCo/executor"; | ||
| 25 | const authorX = `https://x.com/${authorHandle}`; | 17 | const authorX = `https://x.com/${authorHandle}`; |
| 26 | 18 | ||
| 27 | const formattedDate = date.toLocaleDateString("en-US", { | 19 | const formattedDate = date.toLocaleDateString("en-US", { |
| @@ -36,147 +28,29 @@ const formattedDate = date.toLocaleDateString("en-US", { · 8 unmodified lines | |||
| 36 | const isoDate = date.toISOString(); | 28 | const isoDate = date.toISOString(); |
| 37 | --- | 29 | --- |
| 38 | 30 | ||
| 39 | <Layout title={`${title} — Executor`} description={description}> | 31 | <RailLayout title={`${title} — Executor`} description={description} current="blog"> |
| 40 | <main class="bg-soft relative min-h-dvh antialiased"> | 32 | <article class="doc"> |
| 41 | {/* Graph-paper pattern + fade behind the header, matching the homepage */} | 33 | <header class="doc__intro doc__intro--post"> |
| 42 | <div | 34 | <a href="/blog" class="doc__crumb">← Writing</a> |
| 43 | class="pattern-bg pattern-graph" | 35 | <h1 class="doc__title">{title}</h1> |
| 44 | style="position:absolute;top:0;left:0;right:0;height:60vh;z-index:1;pointer-events:none;" | 36 | <p class="doc__lede">{description}</p> |
| 45 | > | 37 | <a href={authorX} target="_blank" rel="noopener noreferrer" class="doc__sig doc__sig--post"> |
| 38 | <img src={authorAvatar} alt="" width="40" height="40" loading="lazy" /> | ||
| 39 | <span class="doc__sig-b"> | ||
| 40 | <strong>{author}</strong> | ||
| 41 | <span>{authorRole} · <time datetime={isoDate}>{formattedDate}</time></span> | ||
| 42 | </span> | ||
| 43 | </a> | ||
| 44 | </header> | ||
| 45 | |||
| 46 | <div class="blog-prose doc__prose"> | ||
| 47 | <slot /> | ||
| 46 | </div> | 48 | </div> |
| 47 | <div | ||
| 48 | style="position:absolute;top:0;left:0;right:0;height:60vh;z-index:2;pointer-events:none;background:linear-gradient(180deg,transparent 0%,transparent 55%,var(--color-surface) 100%);" | ||
| 49 | > | ||
| 50 | </div> | ||
| 51 | |||
| 52 | <div class="relative z-10"> | ||
| 53 | {/* ─── NAV ─── */} | ||
| 54 | <nav> | ||
| 55 | <div | ||
| 56 | class="mx-auto flex h-14 max-w-[1200px] items-center justify-between px-6 sm:px-10" | ||
| 57 | > | ||
| 58 | <a href="/" class="flex items-center gap-2.5"> | ||
| 59 | <img src="/favicon-192.png" alt="" class="h-5 w-5" /> | ||
| 60 | <span class="text-[15px] font-semibold tracking-[-0.01em] text-ink" | ||
| 61 | >executor</span | ||
| 62 | > | ||
| 63 | </a> | ||
| 64 | <div class="flex items-center gap-5"> | ||
| 65 | <a | ||
| 66 | href="/blog" | ||
| 67 | class="text-[14px] font-medium text-ink-2 transition-colors hover:text-ink" | ||
| 68 | >Blog</a | ||
| 69 | > | ||
| 70 | <a | ||
| 71 | href="https://executor.sh/docs" | ||
| 72 | class="hidden text-[14px] font-medium text-ink-2 transition-colors hover:text-ink sm:inline" | ||
| 73 | >Docs</a | ||
| 74 | > | ||
| 75 | <a | ||
| 76 | href={GH} | ||
| 77 | class="text-[14px] font-medium text-ink-2 transition-colors hover:text-ink" | ||
| 78 | >GitHub</a | ||
| 79 | > | ||
| 80 | <a href="/#install" class="btn-primary">Get started →</a> | ||
| 81 | </div> | ||
| 82 | </div> | ||
| 83 | </nav> | ||
| 84 | |||
| 85 | {/* ─── HEADER ─── */} | ||
| 86 | <section class="px-6 pt-16 pb-10 sm:px-10 sm:pt-24 sm:pb-14"> | ||
| 87 | <div class="mx-auto max-w-[720px]"> | ||
| 88 | <a | ||
| 89 | href="/blog" | ||
| 90 | class="eyebrow inline-flex items-center gap-1.5 transition-colors hover:text-ink-2" | ||
| 91 | >← Blog</a | ||
| 92 | > | ||
| 93 | <h1 | ||
| 94 | class="mt-5 text-[clamp(2.2rem,5vw,3.6rem)] font-semibold leading-[1.05] tracking-[-0.025em] text-ink text-balance" | ||
| 95 | > | ||
| 96 | {title} | ||
| 97 | </h1> | ||
| 98 | <p class="mt-5 text-[17px] leading-[1.6] text-ink-2 text-pretty"> | ||
| 99 | {description} | ||
| 100 | </p> | ||
| 101 | <div class="mt-8 flex items-center gap-3"> | ||
| 102 | <a | ||
| 103 | href={authorX} | ||
| 104 | target="_blank" | ||
| 105 | rel="noopener noreferrer" | ||
| 106 | class="shrink-0" | ||
| 107 | aria-label={`${author} on X`} | ||
| 108 | > | ||
| 109 | <img | ||
| 110 | src={authorAvatar} | ||
| 111 | alt={author} | ||
| 112 | width="44" | ||
| 113 | height="44" | ||
| 114 | loading="lazy" | ||
| 115 | class="h-11 w-11 rounded-full object-cover ring-1 ring-rule" | ||
| 116 | /> | ||
| 117 | </a> | ||
| 118 | <div class="min-w-0"> | ||
| 119 | <a | ||
| 120 | href={authorX} | ||
| 121 | target="_blank" | ||
| 122 | rel="noopener noreferrer" | ||
| 123 | class="text-[15px] font-semibold text-ink transition-colors hover:text-accent" | ||
| 124 | >{author}</a | ||
| 125 | > | ||
| 126 | <div class="mt-0.5 text-[13px] leading-snug text-ink-3"> | ||
| 127 | {authorRole} · <time datetime={isoDate}>{formattedDate}</time> | ||
| 128 | </div> | ||
| 129 | </div> | ||
| 130 | </div> | ||
| 131 | </div> | ||
| 132 | </section> | ||
| 133 | 49 | ||
| 134 | {/* ─── ARTICLE ─── */} | 50 | <section class="doc__section"> |
| 135 | <section class="px-6 pt-2 pb-20 sm:px-10 sm:pb-28"> | 51 | <p class="doc__quiet-p"> |
| 136 | <article class="blog-prose mx-auto max-w-[720px]"> | 52 | <a href="/blog">More writing</a> · <a href="/">Back to Executor</a> |
| 137 | <slot /> | 53 | </p> |
| 138 | </article> | 54 | </section> |
| 139 | </section> | 55 | </article> |
| 140 | 56 | </RailLayout> | |
| 141 | {/* ─── BOTTOM CTA ─── */} | ||
| 142 | <section class="border-t border-rule py-16 sm:py-20"> | ||
| 143 | <div class="mx-auto max-w-[720px] px-6 text-center sm:px-10"> | ||
| 144 | <h2 class="section-title mx-auto mb-7 max-w-[20ch]"> | ||
| 145 | Connect any agent to <span class="serif-italic">everything</span>. | ||
| 146 | </h2> | ||
| 147 | <div class="flex flex-wrap items-center justify-center gap-3"> | ||
| 148 | <a href="/#install" class="btn-primary btn-primary--hero" | ||
| 149 | >Get started →</a | ||
| 150 | > | ||
| 151 | <a href={GH} class="btn-secondary">Star on GitHub</a> | ||
| 152 | </div> | ||
| 153 | </div> | ||
| 154 | </section> | ||
| 155 | |||
| 156 | {/* ─── FOOTER ─── */} | ||
| 157 | <footer class="border-t border-rule py-10"> | ||
| 158 | <div | ||
| 159 | class="mx-auto flex max-w-[1200px] flex-col items-start justify-between gap-6 px-6 sm:flex-row sm:items-center sm:px-10" | ||
| 160 | > | ||
| 161 | <div class="flex items-center gap-2.5"> | ||
| 162 | <img src="/favicon-192.png" alt="" class="h-4 w-4 opacity-80" /> | ||
| 163 | <span class="text-[13px] font-medium text-ink-2">executor</span> | ||
| 164 | <span class="ml-2 text-[12px] text-ink-3" | ||
| 165 | >© {new Date().getFullYear()}</span | ||
| 166 | > | ||
| 167 | </div> | ||
| 168 | <div class="flex items-center gap-6 text-[13px] text-ink-2"> | ||
| 169 | <a href="/blog" class="transition-colors hover:text-ink">Blog</a> | ||
| 170 | <a | ||
| 171 | href="https://executor.sh/docs" | ||
| 172 | class="transition-colors hover:text-ink">Docs</a | ||
| 173 | > | ||
| 174 | <a href={GH} class="transition-colors hover:text-ink">GitHub</a> | ||
| 175 | <a href="/privacy" class="transition-colors hover:text-ink">Privacy</a> | ||
| 176 | <a href="/terms" class="transition-colors hover:text-ink">Terms</a> | ||
| 177 | </div> | ||
| 178 | </div> | ||
| 179 | </footer> | ||
| 180 | </div> | ||
| 181 | </main> | ||
| 182 | </Layout> | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | --- | ||
| 2 | // The site shell: sticky rail on the left, one centred frame, footer at the | ||
| 3 | // bottom of the body column. The homepage, pricing, and blog all use it. | ||
| 4 | // Prerendered pages resolve the star count once at build time. | ||
| 5 | import Layout from "./Layout.astro"; | ||
| 6 | import RailB from "../components/variants/rail-b.astro"; | ||
| 7 | import { getStars, formatStars } from "../lib/github"; | ||
| 8 | import "../styles/variants/doc.css"; | ||
| 9 | |||
| 10 | interface Props { | ||
| 11 | title: string; | ||
| 12 | description: string; | ||
| 13 | current?: "home" | "pricing" | "blog"; | ||
| 14 | } | ||
| 15 | const { title, description, current = "home" } = Astro.props; | ||
| 16 | |||
| 17 | const GH = "https://github.com/UsefulSoftwareCo/executor"; | ||
| 18 | const stars = await getStars(); | ||
| 19 | --- | ||
| 20 | |||
| 21 | <Layout title={title} description={description}> | ||
| 22 | <main class="bg-soft min-h-dvh relative antialiased"> | ||
| 23 | <div class="relative z-10 page"> | ||
| 24 | <div class="page__rail page__rail--b"> | ||
| 25 | <RailB stars={stars != null ? formatStars(stars) : null} current={current} /> | ||
| 26 | </div> | ||
| 27 | <div class="page__body"> | ||
| 28 | <slot /> | ||
| 29 | |||
| 30 | <footer class="border-t border-rule py-10"> | ||
| 31 | <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"> | ||
| 32 | <div class="flex items-center gap-2.5"> | ||
| 33 | <img src="/favicon-192.png" alt="" class="w-4 h-4 opacity-80" /> | ||
| 34 | <span class="text-[13px] font-medium text-ink-2">executor</span> | ||
| 35 | <span class="text-[12px] text-ink-3 ml-2">© {new Date().getFullYear()}</span> | ||
| 36 | </div> | ||
| 37 | <div class="flex items-center gap-6 text-[13px] text-ink-2"> | ||
| 38 | <a href="https://executor.sh/docs" class="hover:text-ink transition-colors">Docs</a> | ||
| 39 | <a href="/blog" class="hover:text-ink transition-colors">Blog</a> | ||
| 40 | <a href={GH} class="hover:text-ink transition-colors">GitHub</a> | ||
| 41 | <a href="/privacy" class="hover:text-ink transition-colors">Privacy</a> | ||
| 42 | <a href="/terms" class="hover:text-ink transition-colors">Terms</a> | ||
| 43 | </div> | ||
| 44 | </div> | ||
| 45 | </footer> | ||
| 46 | </div> | ||
| 47 | </div> | ||
| 48 | </main> | ||
| 49 | </Layout> | ||
| @@ -1,10 +1,8 @@ | |||
| 1 | --- | 1 | --- |
| 2 | export const prerender = true; | 2 | export const prerender = true; |
| 3 | import Layout from "../../layouts/Layout.astro"; | 3 | import RailLayout from "../../layouts/RailLayout.astro"; |
| 4 | import { getCollection } from "astro:content"; | 4 | import { getCollection } from "astro:content"; |
| 5 | 5 | ||
| 6 | const GH = "https://github.com/UsefulSoftwareCo/executor"; | ||
| 7 | |||
| 8 | const posts = (await getCollection("blog", ({ data }) => !data.draft)).sort( | 6 | const posts = (await getCollection("blog", ({ data }) => !data.draft)).sort( |
| 9 | (a, b) => b.data.date.getTime() - a.data.date.getTime(), | 7 | (a, b) => b.data.date.getTime() - a.data.date.getTime(), |
| 10 | ); | 8 | ); |
| @@ -20,126 +18,29 @@ const fmt = (date: Date) => · 9 unmodified lines | |||
| 20 | }); | 18 | }); |
| 21 | --- | 19 | --- |
| 22 | 20 | ||
| 23 | <Layout | 21 | <RailLayout title="Blog — Executor" description="Latest news and updates from Executor." current="blog"> |
| 24 | title="Blog — Executor" | 22 | <article class="doc"> |
| 25 | description="Latest news and updates from Executor." | 23 | <header class="doc__intro"> |
| 26 | > | 24 | <h1 class="doc__title">Writing</h1> |
| 27 | <main class="bg-soft relative min-h-dvh antialiased"> | 25 | <p class="doc__lede">Notes from building Executor.</p> |
| 28 | <div | 26 | </header> |
| 29 | class="pattern-bg pattern-graph" | 27 | |
| 30 | style="position:absolute;top:0;left:0;right:0;height:60vh;z-index:1;pointer-events:none;" | 28 | { |
| 31 | > | 29 | posts.length === 0 ? ( |
| 32 | </div> | 30 | <p class="text-[15px] text-ink-3">No posts yet. Check back soon.</p> |
| 33 | <div | 31 | ) : ( |
| 34 | style="position:absolute;top:0;left:0;right:0;height:60vh;z-index:2;pointer-events:none;background:linear-gradient(180deg,transparent 0%,transparent 55%,var(--color-surface) 100%);" | 32 | <ul class="doc__posts"> |
| 35 | > | 33 | {posts.map((post) => ( |
| 36 | </div> | 34 | <li> |
| 37 | 35 | <a href={`/blog/${post.id}`} class="doc__post"> | |
| 38 | <div class="relative z-10"> | 36 | <span class="doc__post-date">{fmt(post.data.date)}</span> |
| 39 | {/* ─── NAV ─── */} | 37 | <span class="doc__post-title">{post.data.title}</span> |
| 40 | <nav> | 38 | <span class="doc__post-desc">{post.data.description}</span> |
| 41 | <div | 39 | </a> |
| 42 | class="mx-auto flex h-14 max-w-[1200px] items-center justify-between px-6 sm:px-10" | 40 | </li> |
| 43 | > | 41 | ))} |
| 44 | <a href="/" class="flex items-center gap-2.5"> | 42 | </ul> |
| 45 | <img src="/favicon-192.png" alt="" class="h-5 w-5" /> | 43 | ) |
| 46 | <span class="text-[15px] font-semibold tracking-[-0.01em] text-ink" | 44 | } |
| 47 | >executor</span | 45 | </article> |
| 48 | > | 46 | </RailLayout> |
| 49 | </a> | ||
| 50 | <div class="flex items-center gap-5"> | ||
| 51 | <a | ||
| 52 | href="/blog" | ||
| 53 | class="text-[14px] font-medium text-ink transition-colors">Blog</a | ||
| 54 | > | ||
| 55 | <a | ||
| 56 | href="https://executor.sh/docs" | ||
| 57 | class="hidden text-[14px] font-medium text-ink-2 transition-colors hover:text-ink sm:inline" | ||
| 58 | >Docs</a | ||
| 59 | > | ||
| 60 | <a | ||
| 61 | href={GH} | ||
| 62 | class="text-[14px] font-medium text-ink-2 transition-colors hover:text-ink" | ||
| 63 | >GitHub</a | ||
| 64 | > | ||
| 65 | <a href="/#install" class="btn-primary">Get started →</a> | ||
| 66 | </div> | ||
| 67 | </div> | ||
| 68 | </nav> | ||
| 69 | |||
| 70 | {/* ─── HEADER ─── */} | ||
| 71 | <section class="px-6 pt-16 pb-10 sm:px-10 sm:pt-24 sm:pb-12"> | ||
| 72 | <div class="mx-auto max-w-[760px]"> | ||
| 73 | <h1 | ||
| 74 | class="text-[clamp(2.4rem,5.6vw,4rem)] font-semibold leading-[1.02] tracking-[-0.025em] text-ink" | ||
| 75 | > | ||
| 76 | Blog | ||
| 77 | </h1> | ||
| 78 | <p class="mt-5 max-w-[54ch] text-[17px] leading-[1.6] text-ink-2 text-pretty"> | ||
| 79 | Latest news and updates from Executor | ||
| 80 | </p> | ||
| 81 | </div> | ||
| 82 | </section> | ||
| 83 | |||
| 84 | {/* ─── POST LIST ─── */} | ||
| 85 | <section class="px-6 pb-24 sm:px-10 sm:pb-32"> | ||
| 86 | <div class="mx-auto max-w-[760px]"> | ||
| 87 | { | ||
| 88 | posts.length === 0 ? ( | ||
| 89 | <p class="text-[15px] text-ink-3">No posts yet. Check back soon.</p> | ||
| 90 | ) : ( | ||
| 91 | <ul class="flex flex-col gap-4"> | ||
| 92 | {posts.map((post) => ( | ||
| 93 | <li> | ||
| 94 | <a | ||
| 95 | href={`/blog/${post.id}`} | ||
| 96 | class="surface-card group block p-6 transition-colors sm:p-7" | ||
| 97 | > | ||
| 98 | <div class="mb-2 font-mono text-[12px] uppercase tracking-[0.14em] text-ink-3"> | ||
| 99 | {fmt(post.data.date)} | ||
| 100 | </div> | ||
| 101 | <h2 class="text-[20px] font-semibold tracking-[-0.01em] text-ink sm:text-[22px]"> | ||
| 102 | {post.data.title} | ||
| 103 | </h2> | ||
| 104 | <p class="mt-2 text-[14.5px] leading-[1.55] text-ink-2 text-pretty"> | ||
| 105 | {post.data.description} | ||
| 106 | </p> | ||
| 107 | <span class="mt-4 inline-flex items-center gap-1 text-[13.5px] font-medium text-ink-2 transition-colors group-hover:text-ink"> | ||
| 108 | Read post → | ||
| 109 | </span> | ||
| 110 | </a> | ||
| 111 | </li> | ||
| 112 | ))} | ||
| 113 | </ul> | ||
| 114 | ) | ||
| 115 | } | ||
| 116 | </div> | ||
| 117 | </section> | ||
| 118 | |||
| 119 | {/* ─── FOOTER ─── */} | ||
| 120 | <footer class="border-t border-rule py-10"> | ||
| 121 | <div | ||
| 122 | class="mx-auto flex max-w-[1200px] flex-col items-start justify-between gap-6 px-6 sm:flex-row sm:items-center sm:px-10" | ||
| 123 | > | ||
| 124 | <div class="flex items-center gap-2.5"> | ||
| 125 | <img src="/favicon-192.png" alt="" class="h-4 w-4 opacity-80" /> | ||
| 126 | <span class="text-[13px] font-medium text-ink-2">executor</span> | ||
| 127 | <span class="ml-2 text-[12px] text-ink-3" | ||
| 128 | >© {new Date().getFullYear()}</span | ||
| 129 | > | ||
| 130 | </div> | ||
| 131 | <div class="flex items-center gap-6 text-[13px] text-ink-2"> | ||
| 132 | <a href="/blog" class="transition-colors hover:text-ink">Blog</a> | ||
| 133 | <a | ||
| 134 | href="https://executor.sh/docs" | ||
| 135 | class="transition-colors hover:text-ink">Docs</a | ||
| 136 | > | ||
| 137 | <a href={GH} class="transition-colors hover:text-ink">GitHub</a> | ||
| 138 | <a href="/privacy" class="transition-colors hover:text-ink">Privacy</a> | ||
| 139 | <a href="/terms" class="transition-colors hover:text-ink">Terms</a> | ||
| 140 | </div> | ||
| 141 | </div> | ||
| 142 | </footer> | ||
| 143 | </div> | ||
| 144 | </main> | ||
| 145 | </Layout> | ||
| @@ -1,10 +1,8 @@ | |||
| 1 | --- | 1 | --- |
| 2 | import Layout from "../layouts/Layout.astro"; | 2 | import RailLayout from "../layouts/RailLayout.astro"; |
| 3 | // Imported so Vite emits it as a hashed build asset (served from /_astro/...); | 3 | // Imported so Vite emits it as a hashed build asset (served from /_astro/...); |
| 4 | // the deploy pipeline does not pick up newly added /public files. | 4 | // the deploy pipeline does not pick up newly added /public files. |
| 5 | import { getStars, formatStars } from "../lib/github"; | ||
| 6 | import { setupPrompt } from "../content/site-copy"; | 5 | import { setupPrompt } from "../content/site-copy"; |
| 7 | import RailB from "../components/variants/rail-b.astro"; | ||
| 8 | import DocBody from "../components/variants/doc-body.astro"; | 6 | import DocBody from "../components/variants/doc-body.astro"; |
| 9 | 7 | ||
| 10 | const GH = "https://github.com/UsefulSoftwareCo/executor"; | 8 | const GH = "https://github.com/UsefulSoftwareCo/executor"; |
| @@ -12,57 +10,16 @@ const GH = "https://github.com/UsefulSoftwareCo/executor"; · 1 unmodified line | |||
| 12 | // /api/* is app-owned (apps/cloud/src/app-paths.ts), so this hits the WorkOS | 10 | // /api/* is app-owned (apps/cloud/src/app-paths.ts), so this hits the WorkOS |
| 13 | // login flow directly. Returning users land here; new users use Get started. | 11 | // login flow directly. Returning users land here; new users use Get started. |
| 14 | const SIGN_IN = "/api/auth/login"; | 12 | const SIGN_IN = "/api/auth/login"; |
| 15 | const stars = await getStars(); | ||
| 16 | 13 | ||
| 17 | --- | 14 | --- |
| 18 | 15 | ||
| 19 | <Layout> | 16 | <RailLayout |
| 20 | <main class="bg-soft min-h-dvh relative antialiased"> | 17 | title="Executor — The gateway to connect your agent to everything" |
| 21 | {/* Graph-paper hero texture: grayscale (ink-masked), faded into the surface | 18 | description="One place every agent plugs into every tool you already use. Wire it up once, bring the whole team." |
| 22 | at the bottom. Part of the registry-grade language, not a colored effect. */} | 19 | > |
| 23 | <div | 20 | <DocBody gh={GH} /> |
| 24 | class="pattern-bg pattern-graph" | 21 | </RailLayout> |
| 25 | style="position: absolute; top: 0; left: 0; right: 0; height: 100vh; z-index: 1; pointer-events: none;" | ||
| 26 | > | ||
| 27 | </div> | ||
| 28 | <div | ||
| 29 | 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%);" | ||
| 30 | > | ||
| 31 | </div> | ||
| 32 | 22 | ||
| 33 | <div class="relative z-10 page"> | ||
| 34 | <div class="page__rail page__rail--b"><RailB stars={stars != null ? formatStars(stars) : null} /></div> | ||
| 35 | <div class="page__body"> | ||
| 36 | |||
| 37 | <DocBody gh={GH} /> | ||
| 38 | |||
| 39 | {/* ─── FOOTER ─── */} | ||
| 40 | <footer class="border-t border-rule py-10"> | ||
| 41 | <div | ||
| 42 | 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" | ||
| 43 | > | ||
| 44 | <div class="flex items-center gap-2.5"> | ||
| 45 | <img src="/favicon-192.png" alt="" class="w-4 h-4 opacity-80" /> | ||
| 46 | <span class="text-[13px] font-medium text-ink-2">executor</span> | ||
| 47 | <span class="text-[12px] text-ink-3 ml-2" | ||
| 48 | >© {new Date().getFullYear()}</span | ||
| 49 | > | ||
| 50 | </div> | ||
| 51 | <div class="flex items-center gap-6 text-[13px] text-ink-2"> | ||
| 52 | <a href="https://executor.sh/docs" class="hover:text-ink transition-colors" | ||
| 53 | >Docs</a | ||
| 54 | > | ||
| 55 | <a href="/blog" class="hover:text-ink transition-colors">Blog</a> | ||
| 56 | <a href={GH} class="hover:text-ink transition-colors">GitHub</a> | ||
| 57 | <a href="/privacy" class="hover:text-ink transition-colors">Privacy</a> | ||
| 58 | <a href="/terms" class="hover:text-ink transition-colors">Terms</a> | ||
| 59 | </div> | ||
| 60 | </div> | ||
| 61 | </footer> | ||
| 62 | </div> | ||
| 63 | </div> | ||
| 64 | </main> | ||
| 65 | </Layout> | ||
| 66 | 23 | ||
| 67 | <script> | 24 | <script> |
| 68 | // Copy buttons. Use the async Clipboard API in secure contexts and fall back | 25 | // Copy buttons. Use the async Clipboard API in secure contexts and fall back |
| @@ -1,13 +1,9 @@ | |||
| 1 | --- | 1 | --- |
| 2 | import Layout from "../layouts/Layout.astro"; | 2 | import RailLayout from "../layouts/RailLayout.astro"; |
| 3 | import { getStars, formatStars } from "../lib/github"; | ||
| 4 | import { pricingTiers } from "../content/site-copy"; | 3 | import { pricingTiers } from "../content/site-copy"; |
| 5 | import RailB from "../components/variants/rail-b.astro"; | ||
| 6 | import "../styles/variants/doc.css"; | ||
| 7 | import "../styles/variants/pricing.css"; | 4 | import "../styles/variants/pricing.css"; |
| 8 | 5 | ||
| 9 | const GH = "https://github.com/UsefulSoftwareCo/executor"; | 6 | const GH = "https://github.com/UsefulSoftwareCo/executor"; |
| 10 | const stars = await getStars(); | ||
| 11 | 7 | ||
| 12 | // Same tiers the Markdown route serves; the card layout only needs a split | 8 | // Same tiers the Markdown route serves; the card layout only needs a split |
| 13 | // between the number and its unit, and a real href for each call to action. | 9 | // between the number and its unit, and a real href for each call to action. |
| @@ -39,91 +35,66 @@ const cards = [ · 25 unmodified lines | |||
| 39 | ]; | 35 | ]; |
| 40 | --- | 36 | --- |
| 41 | 37 | ||
| 42 | <Layout | 38 | <RailLayout |
| 43 | title="Pricing — Executor" | 39 | title="Pricing — Executor" |
| 44 | description="Start free, pay per member. Executor Cloud pricing for teams and enterprises." | 40 | description="Start free, pay per member. Executor Cloud pricing for teams and enterprises." |
| 41 | current="pricing" | ||
| 45 | > | 42 | > |
| 46 | <main class="bg-soft min-h-dvh relative antialiased"> | 43 | <article class="doc"> |
| 47 | <div class="relative z-10 page"> | 44 | <header class="doc__intro"> |
| 48 | <div class="page__rail page__rail--b"> | 45 | <h1 class="doc__title">Start free, pay per member.</h1> |
| 49 | <RailB stars={stars != null ? formatStars(stars) : null} current="pricing" /> | 46 | <p class="doc__lede"> |
| 50 | </div> | 47 | Cloud is free for small teams. Team is one flat price per person. Enterprise is |
| 51 | <div class="page__body"> | 48 | for orgs that need it on their own infrastructure with their own controls. |
| 52 | <article class="doc"> | 49 | </p> |
| 53 | <header class="doc__intro"> | 50 | </header> |
| 54 | <h1 class="doc__title">Start free, pay per member.</h1> | ||
| 55 | <p class="doc__lede"> | ||
| 56 | Cloud is free for small teams. Team is one flat price per person. Enterprise is | ||
| 57 | for orgs that need it on their own infrastructure with their own controls. | ||
| 58 | </p> | ||
| 59 | </header> | ||
| 60 | |||
| 61 | <div class="pricing"> | ||
| 62 | { | ||
| 63 | cards.map(({ tier, amount, unit, href, label, recommended }) => ( | ||
| 64 | <section class:list={["pricing__card", recommended && "pricing__card--rec"]}> | ||
| 65 | <span class="pricing__eyebrow">{recommended ? "Recommended" : tier.name === "Free" ? "Get started" : "Custom needs"}</span> | ||
| 66 | <h2 class="pricing__name">{tier.name}</h2> | ||
| 67 | <p class="pricing__aud">{tier.audience.replace(" (recommended)", "")}</p> | ||
| 68 | <p class="pricing__price"> | ||
| 69 | <span class="pricing__amount">{amount}</span> | ||
| 70 | {unit ? <span class="pricing__unit">{unit}</span> : null} | ||
| 71 | </p> | ||
| 72 | <a href={href} class={recommended ? "btn-primary" : "btn-secondary"}>{label}</a> | ||
| 73 | {tier.featuresLabel ? <p class="pricing__flabel">{tier.featuresLabel}</p> : null} | ||
| 74 | <ul class="check-list"> | ||
| 75 | {tier.features.map((f) => ( | ||
| 76 | <li> | ||
| 77 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg> | ||
| 78 | {f} | ||
| 79 | </li> | ||
| 80 | ))} | ||
| 81 | </ul> | ||
| 82 | </section> | ||
| 83 | )) | ||
| 84 | } | ||
| 85 | </div> | ||
| 86 | |||
| 87 | 51 | ||
| 88 | <section class="doc__section"> | 52 | <div class="pricing"> |
| 89 | <h2 class="doc__h">Questions</h2> | 53 | { |
| 90 | <dl class="doc__beats"> | 54 | cards.map(({ tier, amount, unit, href, label, recommended }) => ( |
| 91 | <div> | 55 | <section class:list={["pricing__card", recommended && "pricing__card--rec"]}> |
| 92 | <dt>What counts as an execution?</dt> | 56 | <span class="pricing__eyebrow">{recommended ? "Recommended" : tier.name === "Free" ? "Get started" : "Custom needs"}</span> |
| 93 | <dd>One call to the Executor tool from your agent. A single execution can make many tool calls inside it.</dd> | 57 | <h2 class="pricing__name">{tier.name}</h2> |
| 94 | </div> | 58 | <p class="pricing__aud">{tier.audience.replace(" (recommended)", "")}</p> |
| 95 | <div> | 59 | <p class="pricing__price"> |
| 96 | <dt>What happens when the free tier runs out?</dt> | 60 | <span class="pricing__amount">{amount}</span> |
| 97 | <dd>Calls pause until the month resets or you move to Team. Nothing is deleted.</dd> | 61 | {unit ? <span class="pricing__unit">{unit}</span> : null} |
| 98 | </div> | ||
| 99 | <div> | ||
| 100 | <dt>Can I mix Cloud and self-hosted?</dt> | ||
| 101 | <dd>Yes. They are the same code, and an agent can point at more than one Executor.</dd> | ||
| 102 | </div> | ||
| 103 | </dl> | ||
| 104 | <p class="doc__quiet-p"> | ||
| 105 | Something else? <a href="mailto:rhys@executor.sh?subject=Executor%20pricing">Email Rhys</a>. | ||
| 106 | </p> | 62 | </p> |
| 63 | <a href={href} class={recommended ? "btn-primary" : "btn-secondary"}>{label}</a> | ||
| 64 | {tier.featuresLabel ? <p class="pricing__flabel">{tier.featuresLabel}</p> : null} | ||
| 65 | <ul class="check-list"> | ||
| 66 | {tier.features.map((f) => ( | ||
| 67 | <li> | ||
| 68 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg> | ||
| 69 | {f} | ||
| 70 | </li> | ||
| 71 | ))} | ||
| 72 | </ul> | ||
| 107 | </section> | 73 | </section> |
| 108 | </article> | 74 | )) |
| 109 | 75 | } | |
| 110 | <footer class="border-t border-rule py-10"> | ||
| 111 | <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"> | ||
| 112 | <div class="flex items-center gap-2.5"> | ||
| 113 | <img src="/favicon-192.png" alt="" class="w-4 h-4 opacity-80" /> | ||
| 114 | <span class="text-[13px] font-medium text-ink-2">executor</span> | ||
| 115 | <span class="text-[12px] text-ink-3 ml-2">© {new Date().getFullYear()}</span> | ||
| 116 | </div> | ||
| 117 | <div class="flex items-center gap-6 text-[13px] text-ink-2"> | ||
| 118 | <a href="https://executor.sh/docs" class="hover:text-ink transition-colors">Docs</a> | ||
| 119 | <a href="/blog" class="hover:text-ink transition-colors">Blog</a> | ||
| 120 | <a href={GH} class="hover:text-ink transition-colors">GitHub</a> | ||
| 121 | <a href="/privacy" class="hover:text-ink transition-colors">Privacy</a> | ||
| 122 | <a href="/terms" class="hover:text-ink transition-colors">Terms</a> | ||
| 123 | </div> | ||
| 124 | </div> | ||
| 125 | </footer> | ||
| 126 | </div> | ||
| 127 | </div> | 76 | </div> |
| 128 | </main> | 77 | |
| 129 | </Layout> | 78 | |
| 79 | <section class="doc__section"> | ||
| 80 | <h2 class="doc__h">Questions</h2> | ||
| 81 | <dl class="doc__beats"> | ||
| 82 | <div> | ||
| 83 | <dt>What counts as an execution?</dt> | ||
| 84 | <dd>One call to the Executor tool from your agent. A single execution can make many tool calls inside it.</dd> | ||
| 85 | </div> | ||
| 86 | <div> | ||
| 87 | <dt>What happens when the free tier runs out?</dt> | ||
| 88 | <dd>Calls pause until the month resets or you move to Team. Nothing is deleted.</dd> | ||
| 89 | </div> | ||
| 90 | <div> | ||
| 91 | <dt>Can I mix Cloud and self-hosted?</dt> | ||
| 92 | <dd>Yes. They are the same code, and an agent can point at more than one Executor.</dd> | ||
| 93 | </div> | ||
| 94 | </dl> | ||
| 95 | <p class="doc__quiet-p"> | ||
| 96 | Something else? <a href="mailto:rhys@executor.sh?subject=Executor%20pricing">Email Rhys</a>. | ||
| 97 | </p> | ||
| 98 | </section> | ||
| 99 | </article> | ||
| 100 | </RailLayout> | ||
| @@ -949,3 +949,69 @@ a.doc__term-v:hover { · 948 unmodified lines | |||
| 949 | text-align: left; | 949 | text-align: left; |
| 950 | } | 950 | } |
| 951 | } | 951 | } |
| 952 | |||
| 953 | /* ── Blog ── */ | ||
| 954 | .doc__posts { | ||
| 955 | display: grid; | ||
| 956 | margin-top: 0.5rem; | ||
| 957 | border-top: 1px solid var(--color-rule); | ||
| 958 | } | ||
| 959 | .doc__post { | ||
| 960 | display: grid; | ||
| 961 | gap: 0.3rem; | ||
| 962 | padding: 1.15rem 0 1.25rem; | ||
| 963 | border-bottom: 1px solid var(--color-rule); | ||
| 964 | text-decoration: none; | ||
| 965 | color: var(--color-ink-2); | ||
| 966 | } | ||
| 967 | .doc__post-date { | ||
| 968 | font-family: var(--font-mono); | ||
| 969 | font-size: 11px; | ||
| 970 | text-transform: uppercase; | ||
| 971 | letter-spacing: 0.14em; | ||
| 972 | color: var(--color-ink-3); | ||
| 973 | } | ||
| 974 | .doc__post-title { | ||
| 975 | font-size: 19px; | ||
| 976 | font-weight: 600; | ||
| 977 | letter-spacing: -0.01em; | ||
| 978 | color: var(--color-ink); | ||
| 979 | text-decoration: underline; | ||
| 980 | text-underline-offset: 4px; | ||
| 981 | text-decoration-color: transparent; | ||
| 982 | transition: text-decoration-color 0.15s ease; | ||
| 983 | } | ||
| 984 | .doc__post:hover .doc__post-title { | ||
| 985 | text-decoration-color: var(--color-rule-strong); | ||
| 986 | } | ||
| 987 | .doc__post-desc { | ||
| 988 | font-size: 15px; | ||
| 989 | line-height: 1.55; | ||
| 990 | text-wrap: pretty; | ||
| 991 | } | ||
| 992 | .doc__crumb { | ||
| 993 | display: inline-block; | ||
| 994 | font-family: var(--font-mono); | ||
| 995 | font-size: 11px; | ||
| 996 | text-transform: uppercase; | ||
| 997 | letter-spacing: 0.14em; | ||
| 998 | color: var(--color-ink-3); | ||
| 999 | margin-bottom: 1rem; | ||
| 1000 | } | ||
| 1001 | .doc__crumb:hover { | ||
| 1002 | color: var(--color-ink); | ||
| 1003 | } | ||
| 1004 | .doc__sig--post { | ||
| 1005 | margin-top: 0.5rem; | ||
| 1006 | } | ||
| 1007 | .doc__intro--post { | ||
| 1008 | padding-bottom: 2rem; | ||
| 1009 | border-bottom: 1px solid var(--color-rule); | ||
| 1010 | margin-bottom: 2rem; | ||
| 1011 | } | ||
| 1012 | @media (min-width: 1024px) { | ||
| 1013 | .doc__prose, | ||
| 1014 | .doc__posts { | ||
| 1015 | max-width: 720px; | ||
| 1016 | } | ||
| 1017 | } | ||