History

feat(marketing): og:image meta tags and URL-synced hero tab

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.

Rhys Sullivan committed 6 months ago

Commit 42674bb1 parent 4cfa2994 files · +31 −0Browse files

Unified · Split

4 files
  1. apps/marketing/astro.config.mjs +1 −0
  2. apps/marketing/public/og-image.png +0 −0
  3. apps/marketing/src/layouts/Layout.astro +14 −0
  4. apps/marketing/src/pages/index.astro +17 −1
apps/marketing/astro.config.mjs+1−0
@@ -8,6 +8,7 @@ import cloudflare from "@astrojs/cloudflare"; · 7 unmodified lines
88
99 // https://astro.build/config
1010 export default defineConfig({
11+ site: "https://executor.sh",
1112 output: "server",
1213 integrations: [react()],
1314 vite: {
apps/marketing/public/og-image.png+0−0

Binary file not shown.

apps/marketing/src/layouts/Layout.astro+14−0
@@ -10,6 +10,9 @@ const { · 9 unmodified lines
1010 title = 'Executor — The gateway to connect your agent to everything',
1111 description = 'One place every agent plugs into every tool you already use. Wire it up once, bring the whole team.'
1212 } = 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()
1316 ---
1417
1518 <html lang="en">
@@ -22,8 +25,19 @@ const { · 6 unmodified lines
2225 <meta name="viewport" content="width=device-width, initial-scale=1" />
2326 <meta name="color-scheme" content="light" />
2427 <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} />
2531 <meta property="og:title" content={title} />
2632 <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} />
2741 <title>{title}</title>
2842 <link rel="preconnect" href="https://fonts.googleapis.com" />
2943 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
apps/marketing/src/pages/index.astro+17−1
@@ -377,7 +377,16 @@ import { SelfHostContactModal } from "../components/self-host-contact-modal"; · 376 unmodified lines
377377 // visitors still see the recommended path.
378378 const heroToggle = document.querySelector<HTMLElement>(".hero-toggle");
379379 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) => {
381390 if (!heroToggle || !tabToggle) return;
382391 heroToggle.dataset.active = tab;
383392 tabToggle.dataset.active = tab;
@@ -389,8 +398,11 @@ import { SelfHostContactModal } from "../components/self-host-contact-modal"; · 5 unmodified lines
389398 );
390399 },
391400 );
401+ if (updateUrl) syncTabToUrl(tab);
392402 };
393403 if (heroToggle && tabToggle) {
404+ const initial = new URLSearchParams(window.location.search).get(TAB_PARAM);
405+ if (isHeroTab(initial)) setActiveTab(initial, false);
394406 tabToggle.querySelectorAll<HTMLButtonElement>("button[data-tab]").forEach(
395407 (btn) => {
396408 btn.addEventListener("click", () => {
@@ -399,6 +411,10 @@ import { SelfHostContactModal } from "../components/self-host-contact-modal"; · 2 unmodified lines
399411 });
400412 },
401413 );
414+ window.addEventListener("popstate", () => {
415+ const next = new URLSearchParams(window.location.search).get(TAB_PARAM);
416+ setActiveTab(isHeroTab(next) ? next : "cloud", false);
417+ });
402418 }
403419
404420 // Desktop card "Download →" CTA jumps to the hero, switches to Local,