diff --git a/src/app/layout.tsx b/src/app/layout.tsx index faa287e..cec0100 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,14 +17,23 @@ const spaceGrotesk = Space_Grotesk({ display: "swap", }); +/** + * Where this site actually serves. Next resolves the generated og:image against + * `metadataBase`; without it the tag is emitted as http://localhost:3000/... — + * present, plausible, and unfetchable by every social scraper. The fallback is + * the real host rather than localhost so a missing env var degrades to correct. + */ +const SITE_URL = process.env.NEXT_PUBLIC_APP_URL || "https://solon.orangecat.ch"; + export const metadata: Metadata = { + metadataBase: new URL(SITE_URL), title: "Solon — Bitcoin-Native Governance", description: "Radical transparency and cryptographic democracy for organizations.", icons: [{ rel: "icon", url: "/favicon.ico" }], openGraph: { title: "Solon", description: "Bitcoin-Native Governance for the Digital Age", - url: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000", + url: SITE_URL, siteName: "Solon", }, }; diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx new file mode 100644 index 0000000..a5459cb --- /dev/null +++ b/src/app/opengraph-image.tsx @@ -0,0 +1,97 @@ +import { ImageResponse } from "next/og"; + +/** + * Social preview card, generated at request time by Next's file convention. + * + * Solon had no og:image at all, so every link to it — Telegram, Slack, a DM — + * rendered as a blank grey rectangle. 1200×630 is the canonical 1.91:1 card + * size all three of those scrapers crop to. + * + * Satori cannot read CSS custom properties, so the palette is repeated here as + * literals. globals.css remains the source of truth; these mirror it and are + * the only non-CSS consumer of the tokens. + */ + +export const runtime = "edge"; +export const alt = "Solon — Bitcoin-Native Governance"; +export const size = { width: 1200, height: 630 }; +export const contentType = "image/png"; + +// Mirrors --solon-dark / --solon-orange / --solon-bitcoin in src/app/globals.css +const INK = "#0F172A"; +const ORANGE = "#F97316"; +const BITCOIN = "#F7931A"; +const MUTED = "#94A3B8"; + +export default function OGImage() { + return new ImageResponse( + ( +