Ship a real OG/Twitter card image for executor.sh and persist the hero Cloud/Local toggle to `?try=`, so the choice is shareable and survives back/forward navigation.
Unified · Split
| @@ -8,6 +8,7 @@ import cloudflare from "@astrojs/cloudflare"; · 7 unmodified lines | ||
| 8 | 8 | |
| 9 | 9 | // https://astro.build/config |
| 10 | 10 | export default defineConfig({ |
| 11 | + site: "https://executor.sh", | |
| 11 | 12 | output: "server", |
| 12 | 13 | integrations: [react()], |
| 13 | 14 | vite: { |
Binary file not shown.
| @@ -10,6 +10,9 @@ const { · 9 unmodified lines | ||
| 10 | 10 | title = 'Executor — The gateway to connect your agent to everything', |
| 11 | 11 | description = 'One place every agent plugs into every tool you already use. Wire it up once, bring the whole team.' |
| 12 | 12 | } = Astro.props |
| 13 | + | |
| 14 | +const ogImage = new URL('/og-image.png', Astro.site ?? Astro.url).toString() | |
| 15 | +const canonical = new URL(Astro.url.pathname, Astro.site ?? Astro.url).toString() | |
| 13 | 16 | --- |
| 14 | 17 | |
| 15 | 18 | <html lang="en"> |
| @@ -22,8 +25,19 @@ const { · 6 unmodified lines | ||
| 22 | 25 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 23 | 26 | <meta name="color-scheme" content="light" /> |
| 24 | 27 | <meta name="description" content={description} /> |
| 28 | + <link rel="canonical" href={canonical} /> | |
| 29 | + <meta property="og:type" content="website" /> | |
| 30 | + <meta property="og:url" content={canonical} /> | |
| 25 | 31 | <meta property="og:title" content={title} /> |
| 26 | 32 | <meta property="og:description" content={description} /> |
| 33 | + <meta property="og:image" content={ogImage} /> | |
| 34 | + <meta property="og:image:width" content="3800" /> | |
| 35 | + <meta property="og:image:height" content="2360" /> | |
| 36 | + <meta property="og:image:alt" content={title} /> | |
| 37 | + <meta name="twitter:card" content="summary_large_image" /> | |
| 38 | + <meta name="twitter:title" content={title} /> | |
| 39 | + <meta name="twitter:description" content={description} /> | |
| 40 | + <meta name="twitter:image" content={ogImage} /> | |
| 27 | 41 | <title>{title}</title> |
| 28 | 42 | <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| 29 | 43 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| @@ -377,7 +377,16 @@ import { SelfHostContactModal } from "../components/self-host-contact-modal"; · 376 unmodified lines | ||
| 377 | 377 | // visitors still see the recommended path. |
| 378 | 378 | const heroToggle = document.querySelector<HTMLElement>(".hero-toggle"); |
| 379 | 379 | const tabToggle = heroToggle?.querySelector<HTMLElement>(".tab-toggle"); |
| 380 | − const setActiveTab = (tab: "cloud" | "local") => { | |
| 380 | + const TAB_PARAM = "try"; | |
| 381 | + const isHeroTab = (v: unknown): v is "cloud" | "local" => | |
| 382 | + v === "cloud" || v === "local"; | |
| 383 | + const syncTabToUrl = (tab: "cloud" | "local") => { | |
| 384 | + const url = new URL(window.location.href); | |
| 385 | + if (url.searchParams.get(TAB_PARAM) === tab) return; | |
| 386 | + url.searchParams.set(TAB_PARAM, tab); | |
| 387 | + window.history.replaceState(window.history.state, "", url); | |
| 388 | + }; | |
| 389 | + const setActiveTab = (tab: "cloud" | "local", updateUrl = true) => { | |
| 381 | 390 | if (!heroToggle || !tabToggle) return; |
| 382 | 391 | heroToggle.dataset.active = tab; |
| 383 | 392 | tabToggle.dataset.active = tab; |
| @@ -389,8 +398,11 @@ import { SelfHostContactModal } from "../components/self-host-contact-modal"; · 5 unmodified lines | ||
| 389 | 398 | ); |
| 390 | 399 | }, |
| 391 | 400 | ); |
| 401 | + if (updateUrl) syncTabToUrl(tab); | |
| 392 | 402 | }; |
| 393 | 403 | if (heroToggle && tabToggle) { |
| 404 | + const initial = new URLSearchParams(window.location.search).get(TAB_PARAM); | |
| 405 | + if (isHeroTab(initial)) setActiveTab(initial, false); | |
| 394 | 406 | tabToggle.querySelectorAll<HTMLButtonElement>("button[data-tab]").forEach( |
| 395 | 407 | (btn) => { |
| 396 | 408 | btn.addEventListener("click", () => { |
| @@ -399,6 +411,10 @@ import { SelfHostContactModal } from "../components/self-host-contact-modal"; · 2 unmodified lines | ||
| 399 | 411 | }); |
| 400 | 412 | }, |
| 401 | 413 | ); |
| 414 | + window.addEventListener("popstate", () => { | |
| 415 | + const next = new URLSearchParams(window.location.search).get(TAB_PARAM); | |
| 416 | + setActiveTab(isHeroTab(next) ? next : "cloud", false); | |
| 417 | + }); | |
| 402 | 418 | } |
| 403 | 419 | |
| 404 | 420 | // Desktop card "Download →" CTA jumps to the hero, switches to Local, |