Skip to content

Commit 32a94aa

Browse files
jayhackcursoragent
andauthored
docs: add generated og:image for the site (#163)
## Summary Adds a social/OG card for the site, generated deterministically with `next/og` (no AI image gen, so the logo and text are exact and crisp). `site/app/opengraph-image.tsx` renders a 1200×630 PNG with: - the **Codegen brand mark**, - the **Graph-sitter** wordmark, - the tagline **"Industrial-grade static analysis toolkit"**, and - a **node-graph network** background with a purple glow. Next automatically wires this file into `og:image` and `twitter:image` (the layout already declares `metadataBase` + a `summary_large_image` twitter card), so it covers `/`, `/docs`, and `/explore`. ## Test plan - [x] `npm run build` in `site/` passes; `/opengraph-image` prerenders to a valid 1200×630 PNG. - [ ] Verify the rendered card visually (locally hit `/opengraph-image`). - [ ] Verify the card in a social/OG debugger after deploy. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: jayhack <2548876+jayhack@users.noreply.github.com>
1 parent f9b00cd commit 32a94aa

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

site/app/opengraph-image.tsx

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { ImageResponse } from "next/og";
2+
3+
export const alt = "Graph-sitter — industrial-grade static analysis toolkit";
4+
export const size = { width: 1200, height: 630 };
5+
export const contentType = "image/png";
6+
7+
const WIDTH = 1200;
8+
const HEIGHT = 630;
9+
10+
// Deterministic node-graph laid out mostly to the right so it reads as a
11+
// network behind the wordmark without fighting the text on the left.
12+
const nodes: { x: number; y: number; r: number; c: string; o: number }[] = [
13+
{ x: 120, y: 92, r: 5, c: "#a277ff", o: 0.5 },
14+
{ x: 78, y: 520, r: 6, c: "#82e2ff", o: 0.45 },
15+
{ x: 300, y: 560, r: 5, c: "#61ffca", o: 0.5 },
16+
{ x: 760, y: 92, r: 7, c: "#a277ff", o: 0.95 },
17+
{ x: 900, y: 168, r: 9, c: "#82e2ff", o: 0.95 },
18+
{ x: 1044, y: 110, r: 6, c: "#ffca85", o: 0.9 },
19+
{ x: 980, y: 300, r: 12, c: "#a277ff", o: 0.95 },
20+
{ x: 1124, y: 380, r: 7, c: "#61ffca", o: 0.9 },
21+
{ x: 860, y: 432, r: 8, c: "#82e2ff", o: 0.95 },
22+
{ x: 1020, y: 520, r: 6, c: "#a277ff", o: 0.9 },
23+
{ x: 724, y: 560, r: 7, c: "#ffca85", o: 0.9 },
24+
{ x: 700, y: 300, r: 9, c: "#a277ff", o: 0.95 },
25+
{ x: 560, y: 198, r: 5, c: "#6f6b80", o: 0.6 },
26+
{ x: 600, y: 460, r: 6, c: "#82e2ff", o: 0.85 },
27+
{ x: 1140, y: 232, r: 5, c: "#6f6b80", o: 0.6 },
28+
{ x: 840, y: 250, r: 5, c: "#61ffca", o: 0.85 },
29+
{ x: 470, y: 92, r: 4, c: "#6f6b80", o: 0.5 },
30+
{ x: 432, y: 540, r: 5, c: "#a277ff", o: 0.7 },
31+
];
32+
33+
const edges: [number, number][] = [
34+
[3, 4],
35+
[4, 5],
36+
[4, 6],
37+
[6, 7],
38+
[6, 8],
39+
[8, 9],
40+
[8, 10],
41+
[6, 11],
42+
[11, 3],
43+
[11, 15],
44+
[15, 4],
45+
[8, 13],
46+
[13, 10],
47+
[11, 12],
48+
[12, 16],
49+
[11, 5],
50+
[6, 14],
51+
[7, 9],
52+
[10, 9],
53+
[8, 15],
54+
[13, 17],
55+
[17, 2],
56+
[12, 0],
57+
[1, 17],
58+
[14, 7],
59+
[3, 15],
60+
];
61+
62+
function networkDataUri() {
63+
const lines = edges
64+
.map(([a, b]) => {
65+
const p = nodes[a];
66+
const q = nodes[b];
67+
return `<line x1="${p.x}" y1="${p.y}" x2="${q.x}" y2="${q.y}" stroke="#8f74ff" stroke-width="1.4" stroke-opacity="0.42"/>`;
68+
})
69+
.join("");
70+
const dots = nodes
71+
.map(
72+
(n) =>
73+
`<circle cx="${n.x}" cy="${n.y}" r="${n.r}" fill="${n.c}" fill-opacity="${n.o}"/>`,
74+
)
75+
.join("");
76+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}"><defs><radialGradient id="glow" cx="32%" cy="42%" r="68%"><stop offset="0%" stop-color="#7c4dff" stop-opacity="0.42"/><stop offset="48%" stop-color="#7c4dff" stop-opacity="0.10"/><stop offset="100%" stop-color="#0a0a0f" stop-opacity="0"/></radialGradient></defs><rect width="${WIDTH}" height="${HEIGHT}" fill="#0a0a0f"/><rect width="${WIDTH}" height="${HEIGHT}" fill="url(#glow)"/><g>${lines}</g><g>${dots}</g></svg>`;
77+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
78+
}
79+
80+
const markSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80.87 80.87" fill="#ffffff"><path d="M57.34,27.51c-.79.79-.79,2.07,0,2.86l8.64,8.64c.79.79.79,2.07,0,2.86l-8.64,8.64c-.79.79-.79,2.07,0,2.86l4.29,4.29c.79.79,2.07.79,2.86,0l15.79-15.79c.79-.79.79-2.07,0-2.86l-15.79-15.79c-.79-.79-2.07-.79-2.86,0,0,0-4.29,4.29-4.29,4.29Z"/><path d="M50.19,60.51c-.79-.79-2.07-.79-2.86,0l-5.47,5.47c-.79.79-2.07.79-2.86,0l-24.12-24.12c-.79-.79-.79-2.07,0-2.86l24.12-24.12c.79-.79,2.07-.79,2.86,0l5.47,5.47c.79.79,2.07.79,2.86,0l4.29-4.29c.79-.79.79-2.07,0-2.86L41.86.59c-.79-.79-2.07-.79-2.86,0L.59,39.01c-.79.79-.79,2.07,0,2.86l38.41,38.41c.79.79,2.07.79,2.86,0l12.62-12.62c.79-.79.79-2.07,0-2.86,0,0-4.29-4.29-4.29-4.29Z"/><path d="M50.54,40.44c0,5.58-4.53,10.11-10.11,10.11s-10.11-4.53-10.11-10.11,4.53-10.11,10.11-10.11,10.11,4.53,10.11,10.11Z"/></svg>`;
81+
const markUri = `data:image/svg+xml,${encodeURIComponent(markSvg)}`;
82+
83+
export default function OpengraphImage() {
84+
return new ImageResponse(
85+
<div
86+
style={{
87+
width: "100%",
88+
height: "100%",
89+
display: "flex",
90+
position: "relative",
91+
backgroundColor: "#0a0a0f",
92+
}}
93+
>
94+
{/* biome-ignore lint/a11y/useAltText: decorative background in OG image */}
95+
<img
96+
src={networkDataUri()}
97+
width={WIDTH}
98+
height={HEIGHT}
99+
style={{ position: "absolute", top: 0, left: 0 }}
100+
/>
101+
102+
<div
103+
style={{
104+
display: "flex",
105+
flexDirection: "column",
106+
justifyContent: "center",
107+
height: "100%",
108+
padding: "0 96px",
109+
}}
110+
>
111+
<div style={{ display: "flex", alignItems: "center" }}>
112+
{/* biome-ignore lint/a11y/useAltText: decorative logo in OG image */}
113+
<img src={markUri} width={104} height={104} />
114+
<div
115+
style={{
116+
marginLeft: 36,
117+
fontSize: 98,
118+
fontWeight: 700,
119+
color: "#ffffff",
120+
letterSpacing: -3,
121+
}}
122+
>
123+
Graph-sitter
124+
</div>
125+
</div>
126+
<div
127+
style={{
128+
marginTop: 26,
129+
fontSize: 42,
130+
color: "#c4c1cc",
131+
letterSpacing: -0.5,
132+
}}
133+
>
134+
Industrial-grade static analysis toolkit
135+
</div>
136+
</div>
137+
</div>,
138+
{ ...size },
139+
);
140+
}

0 commit comments

Comments
 (0)