1import { ImageResponse } from 'next/og';2import { api, safe } from '@/lib/api';3import { fmtInt, num } from '@/lib/format';4import { SITE_NAME, TAGLINE } from '@/lib/site';56export const runtime = 'nodejs';7export const alt = `${SITE_NAME} — ${TAGLINE}`;8export const size = { width: 1200, height: 630 };9export const contentType = 'image/png';1011/** Default Open Graph image: brand + live counters from /stats when the API is reachable (brand only otherwise). */12export default async function OpenGraphImage() {13 const stats = await safe(api.stats());14 const counters: [string, string][] = stats15 ? [16 ['Models', fmtInt(stats.entities?.model)],17 ['Companies', fmtInt(num(stats.entities?.company) ?? num(stats.entities?.organization))],18 ['Papers', fmtInt(stats.entities?.paper)],19 ['Change events', fmtInt(stats.change_events)],20 ['Sources', fmtInt(stats.sources)],21 ].filter((c) => c[1] !== '—') as [string, string][]22 : [];2324 return new ImageResponse(25 (26 <div27 style={{28 width: '100%',29 height: '100%',30 display: 'flex',31 flexDirection: 'column',32 justifyContent: 'space-between',33 padding: '64px 72px',34 background: 'linear-gradient(160deg, #12151b 0%, #0b0d11 55%, #0b0d11 100%)',35 color: '#e9ebf0',36 fontFamily: 'sans-serif',37 position: 'relative',38 }}39 >40 <div style={{ position: 'absolute', right: -140, top: -160, width: 640, height: 640, borderRadius: 999, background: 'radial-gradient(circle at 40% 40%, rgba(109,149,255,0.18), rgba(11,13,17,0) 70%)', display: 'flex' }} />41 <div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>42 <svg width="64" height="64" viewBox="0 0 32 32" fill="none">43 <circle cx="16" cy="16" r="13" stroke="#e9ebf0" strokeWidth="1.6" />44 <path d="M16 3c-4.4 3.2-6.6 7.6-6.6 13s2.2 9.8 6.6 13M16 3c4.4 3.2 6.6 7.6 6.6 13s-2.2 9.8-6.6 13M3 16h26" stroke="#e9ebf0" strokeWidth="1.2" opacity="0.7" />45 <path d="M9.4 9.5L16 16l6.6-6.5M16 16l-5.2 8.4M16 16l7.4 5.6" stroke="#e9ebf0" strokeWidth="1.2" opacity="0.55" />46 <circle cx="16" cy="16" r="2.4" fill="#6d95ff" />47 <circle cx="9.4" cy="9.5" r="1.7" fill="#e9ebf0" />48 <circle cx="22.6" cy="9.5" r="1.7" fill="#e9ebf0" />49 <circle cx="10.8" cy="24.4" r="1.7" fill="#e9ebf0" />50 <circle cx="23.4" cy="21.6" r="1.7" fill="#e9ebf0" />51 </svg>52 <div style={{ display: 'flex', fontSize: 38, fontWeight: 600, letterSpacing: -1 }}>53 <span>AI</span>54 <span style={{ color: '#a3a9b8', marginLeft: 10, fontWeight: 500 }}>Atlas</span>55 </div>56 </div>57 <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>58 <div style={{ fontSize: 68, fontWeight: 600, lineHeight: 1.02, letterSpacing: -2.5, maxWidth: 940, display: 'flex' }}>{TAGLINE}.</div>59 <div style={{ fontSize: 25, color: '#a3a9b8', display: 'flex' }}>Models · companies · research · providers & pricing · benchmarks · hardware — with provenance and history</div>60 </div>61 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', color: '#6f7688', fontSize: 22 }}>62 <span>www.ai-atlas.co</span>63 {counters.length > 0 && (64 <div style={{ display: 'flex', gap: 40 }}>65 {counters.map(([label, value]) => (66 <div key={label} style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>67 <span style={{ fontSize: 34, fontWeight: 600, color: '#e9ebf0', letterSpacing: -1 }}>{value}</span>68 <span style={{ fontSize: 15, letterSpacing: 2, textTransform: 'uppercase' }}>{label}</span>69 </div>70 ))}71 </div>72 )}73 </div>74 </div>75 ),76 { ...size },77 );78}79