+
+
+
+
+ BUILD CANADA
+
+
+
+ {label}
+
+
+
+
+ {title}
+
+
+
+ {description}
+
+
+
+
+ {chart && (
+
+
+
+ {chartHeading}
+
+ {legend ? (
+
+ {legend.map((item) => (
+
+ ))}
+
+ ) : chart.latestLabel ? (
+
+ {chart.latestLabel}
+
+ ) : null}
+
+
+

+
+
+ {chart.minLabel}
+ {chart.maxLabel}
+
+
+ )}
+
+
+
+ {footnote ?? ""}
+
+
+ buildcanada.com
+
+
+
+ );
+}
+
+// Same Google Fonts subsetting trick as the bills OG images: fetch only the
+// glyphs the card renders, fall back to satori's default font on failure.
+async function loadGoogleFont(font: string, weight: number, text: string) {
+ const params = new URLSearchParams({
+ family: `${font}:wght@${weight}`,
+ text,
+ });
+ const css = await (
+ await fetch(`https://fonts.googleapis.com/css2?${params.toString()}`)
+ ).text();
+ const resource = css.match(
+ /src: url\((.+?)\) format\('(opentype|truetype|woff2)'\)/,
+ );
+ if (resource) {
+ const res = await fetch(resource[1]);
+ if (res.status === 200) return await res.arrayBuffer();
+ }
+ throw new Error("failed to load font data");
+}
+
+export async function loadOgFonts(text: string) {
+ const subset = `${text} BUILDCANAbuildcanada.com0123456789·$%,.()—–&`;
+ try {
+ const [regular, bold] = await Promise.all([
+ loadGoogleFont("Inter", 400, subset),
+ loadGoogleFont("Inter", 700, subset),
+ ]);
+ return [
+ { name: "Inter", data: regular, weight: 400 as const, style: "normal" as const },
+ { name: "Inter", data: bold, weight: 700 as const, style: "normal" as const },
+ ];
+ } catch {
+ return undefined;
+ }
+}
diff --git a/src/app/economic-indicators/opengraph-image.tsx b/src/app/economic-indicators/opengraph-image.tsx
new file mode 100644
index 0000000..9d22d31
--- /dev/null
+++ b/src/app/economic-indicators/opengraph-image.tsx
@@ -0,0 +1,57 @@
+import { ImageResponse } from "next/og";
+import { getEconomicSeries, humanizeSourceName } from "@/lib/api/economy";
+import { indicatorHeading } from "./indicators";
+import {
+ buildOgChart,
+ IndicatorOgCard,
+ loadOgFonts,
+ OG_SIZE,
+} from "./og-card";
+
+export const runtime = "nodejs";
+
+export const alt =
+ "Build Canada — Economic Indicators: Canadian prosperity, measured against the G7 and OECD";
+
+export const size = OG_SIZE;
+
+export const contentType = "image/png";
+
+export const revalidate = 3600;
+
+// The landing card leads with the dashboard's clearest single chart.
+const FEATURED_SLUG = "gdp-per-capita-ppp";
+
+const TITLE = "Economic Indicators";
+const DESCRIPTION =
+ "Are we moving in the right direction? Canadian prosperity, measured against the G7 and OECD.";
+
+export default async function OpengraphImage() {
+ const response = await getEconomicSeries(FEATURED_SLUG).catch(() => null);
+ const chart = response ? buildOgChart(response) : null;
+ const chartHeading = indicatorHeading(FEATURED_SLUG);
+ const source = response?.meta.source;
+ const footnote = source ? `Source: ${humanizeSourceName(source.name)}` : "";
+
+ const fonts = await loadOgFonts(
+ `${TITLE}${DESCRIPTION}${chartHeading}${chart?.latestLabel ?? ""}${footnote}`,
+ );
+
+ return new ImageResponse(
+