import { ImageResponse } from '@vercel/og'; import { NextRequest } from 'next/server'; import { AnisotaIcon, DropChevron, Headline, loadGoogleFont, OgFrame, OG_COLORS, TopRow, UrlPill, WaypointRow, } from '@/lib/og-design'; import type { ReactNode } from 'react'; export const runtime = 'edge'; export const revalidate = 86400; type PageConfig = { eyebrow: string; title: string; tagline: string; /** Promotional product visual shown beneath the headline. */ visual: ReactNode; }; /** * Promotional OG cards for the site's landing pages. Every card shares the * same top row (leaf wordmark + uppercase product label), a serif headline, * a tagline, and a product-specific visual mock further down. */ function configFor(page: string): PageConfig { switch (page) { case 'explore': return { eyebrow: 'Atmosphere data explorer', title: 'Browse every PDS.', tagline: 'Records, identity history, backlinks, and a live view of network activity. For any account in the Atmosphere.', visual: , }; case 'extension': return { eyebrow: 'Browser extension', title: 'Jump between clients\nin one click.', tagline: 'Land on any post and pop open the curated picker. Auto-redirect by lexicon, inspect the underlying AT URI, copy a universal link.', visual: , }; case 'universal-links': return { eyebrow: 'Universal links', title: 'One link, every\nAtmosphere client.', tagline: 'Share aturi.to/handle/collection/rkey with anyone — they pick where to open it, from a curated list of 25+ clients.', visual: , }; case 'integrate': return { eyebrow: 'Integrate', title: 'Add universal sharing\nto your app.', tagline: 'A simple URL pattern your atproto app can adopt to make every record shareable across the Atmosphere.', visual: , }; case 'fork': return { eyebrow: 'Fork & deploy', title: 'Run your own\ninstance.', tagline: 'Open source, environment-driven branding, and ready to deploy on a custom domain.', visual: , }; case 'home': default: return { eyebrow: 'Atmosphere fast travel', title: 'Tour the\nAtmosphere.', tagline: 'Travel between clients with the browser extension, share universal links, and explore any account’s PDS data.', visual: , }; } } // ─── Page visuals ────────────────────────────────────────────────────────── function HomeVisual() { // Mirrors the homepage WaypointJumpVisual: aturi.to URL pill on top, // chevron, row of waypoint icons with one highlighted to show the // "which client?" pick that aturi enables. return (
); } function UniversalLinksVisual() { return ; } function ExploreVisual() { // AT-URI styled breadcrumb — the explorer's signature navigation // pattern, rendered as a horizontal pill so it scans like the // breadcrumb you'd see inside the app. const segments: { label: string; muted?: boolean }[] = [ { label: 'pds.atpota.to' }, { label: '@dame.is' }, { label: 'app.bsky.actor.profile' }, { label: 'self', muted: true }, ]; return (
{segments.map((s, i) => (
{i > 0 && ( › )} {s.label}
))}
); } function ExtensionVisual() { // Compact extension-popup mock: header strip, source URI, a // "Recommended" row with the Anisota icon (matches the live popup's // default recommendation for posts). return (
{/* Popup header */}
Aturi
app.bsky.feed.post
{/* Source URI strip */}
at://  dame.is / app.bsky.feed.post / 3lq9…
{/* Recommended section */}
★ Recommended for posts
} featured />
); } function RowChip({ name, desc, icon, featured, }: { name: string; desc: string; icon: ReactNode; featured?: boolean; }) { return (
{icon}
{name} {desc}
); } // ─── Route ──────────────────────────────────────────────────────────────── export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const page = searchParams.get('page') || 'home'; const config = configFor(page); const allText = `${config.title} ${config.tagline} ${config.eyebrow} aturi.to ` + 'Anisota Bluesky Leaflet Tangled Margin Deer Grain Recommended for posts ' + 'pds.atpota.to dame.is app.bsky.feed.post app.bsky.actor.profile self at:// › ★ ▾'; const fontData = await loadGoogleFont('Crimson+Pro:wght@300;400;600', allText); return new ImageResponse( (
{config.visual}
), { width: 1200, height: 630, fonts: [ { name: 'Crimson Pro', data: fontData, style: 'normal', weight: 300, }, ], }, ); } catch (error) { console.error('Error generating OG image:', error); return new Response('Error generating image', { status: 500 }); } }