Skip to content

Commit ac4bd43

Browse files
committed
feat: add Open Graph image generation for domain reports and main branding
1 parent 06ab906 commit ac4bd43

File tree

5 files changed

+471
-1
lines changed

5 files changed

+471
-1
lines changed

app/[domain]/opengraph-image.tsx

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { ImageResponse } from "next/og";
2+
import { LogoSimple } from "@/components/logo";
3+
import { normalizeDomainInput } from "@/lib/domain";
4+
import { BRAND, CHIPS, hexToRGBA, loadFontsForOG } from "@/lib/opengraph-image";
5+
6+
export const alt = "Domainstack — Domain Report";
7+
export const size = { width: 1200, height: 630 };
8+
export const contentType = "image/png";
9+
10+
export default async function OGImage({
11+
params,
12+
}: {
13+
params: Promise<{ domain: string }>;
14+
}) {
15+
const { domain } = await params;
16+
const decoded = decodeURIComponent(domain);
17+
const normalized = normalizeDomainInput(decoded);
18+
19+
const fonts = await loadFontsForOG();
20+
21+
return new ImageResponse(
22+
<div
23+
style={{
24+
width: "100%",
25+
height: "100%",
26+
display: "flex",
27+
position: "relative",
28+
background: BRAND.bg,
29+
fontFamily: "Geist", // must match fonts[].name
30+
}}
31+
>
32+
{/* Ambient glows: use backgroundImage with rgba alphas (Satori-safe) */}
33+
<div
34+
style={{
35+
position: "absolute",
36+
inset: "-10%",
37+
backgroundColor: "transparent",
38+
// Multiple backgrounds: each radial-gradient adds a soft glow
39+
backgroundImage: [
40+
`radial-gradient(900px 520px at 12% 18%, ${BRAND.a} 0%, rgba(0,0,0,0) 60%)`,
41+
`radial-gradient(1000px 560px at 86% 24%, ${BRAND.b} 0%, rgba(0,0,0,0) 62%)`,
42+
`radial-gradient(800px 520px at 50% 112%, ${BRAND.c} 0%, rgba(0,0,0,0) 62%)`,
43+
// vignette for edge depth
44+
`radial-gradient(1400px 700px at 50% 50%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.35) 100%)`,
45+
].join(","),
46+
}}
47+
/>
48+
49+
{/* Content */}
50+
<div
51+
style={{
52+
display: "flex",
53+
flexDirection: "column",
54+
justifyContent: "space-between",
55+
width: "100%",
56+
height: "100%",
57+
padding: "56px 64px",
58+
}}
59+
>
60+
{/* Header */}
61+
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
62+
<LogoSimple
63+
width={40}
64+
height={40}
65+
style={{
66+
color: BRAND.fg,
67+
display: "block",
68+
borderRadius: 12,
69+
background: "linear-gradient(135deg, #1A1F27 0%, #0F1216 100%)",
70+
boxShadow:
71+
"0 8px 40px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.06)",
72+
padding: 8,
73+
}}
74+
/>
75+
<div style={{ display: "flex", flexDirection: "column" }}>
76+
<div
77+
style={{
78+
fontSize: 22,
79+
color: BRAND.fg,
80+
letterSpacing: 0.3,
81+
fontWeight: 600,
82+
}}
83+
>
84+
Domainstack
85+
</div>
86+
<div style={{ fontSize: 14, color: BRAND.sub }}>
87+
Domain Intelligence Made Easy
88+
</div>
89+
</div>
90+
</div>
91+
92+
{/* Title + subtitle + chips */}
93+
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
94+
<div
95+
style={{
96+
fontSize: 84,
97+
lineHeight: 1.05,
98+
fontWeight: 700,
99+
color: BRAND.fg,
100+
letterSpacing: -1.2,
101+
textShadow: "0 2px 16px rgba(0,0,0,0.35)",
102+
maxWidth: 980,
103+
whiteSpace: "nowrap",
104+
overflow: "hidden",
105+
textOverflow: "ellipsis",
106+
}}
107+
title={normalized}
108+
>
109+
{normalized}
110+
</div>
111+
112+
<div
113+
style={{
114+
display: "flex",
115+
fontSize: 24,
116+
lineHeight: 1.5,
117+
color: BRAND.sub,
118+
maxWidth: 990,
119+
}}
120+
>
121+
Unlock full domain intelligence for {normalized}, including:
122+
</div>
123+
124+
<div
125+
style={{ display: "flex", flexWrap: "wrap", gap: 10, marginTop: 6 }}
126+
>
127+
{CHIPS.map(({ label, color }) => (
128+
<div
129+
key={label}
130+
style={{
131+
fontSize: 20,
132+
color: BRAND.fg,
133+
padding: "10px 16px",
134+
borderRadius: 999,
135+
background: `linear-gradient(180deg, ${hexToRGBA(color, 0.1)} 0%, rgba(255,255,255,0.03) 100%)`,
136+
border: `1px solid ${hexToRGBA(color, 0.28)}`,
137+
boxShadow:
138+
"0 1px 0 rgba(255,255,255,0.05) inset, 0 8px 24px rgba(0,0,0,0.35)",
139+
}}
140+
>
141+
{label}
142+
</div>
143+
))}
144+
</div>
145+
</div>
146+
147+
{/* Footer */}
148+
<div
149+
style={{
150+
display: "flex",
151+
alignItems: "center",
152+
justifyContent: "space-between",
153+
borderTop: "1px solid rgba(255,255,255,0.08)",
154+
paddingTop: 18,
155+
}}
156+
>
157+
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
158+
<div
159+
style={{
160+
width: 10,
161+
height: 10,
162+
borderRadius: 999,
163+
background: "rgba(0, 212, 255, 1)",
164+
boxShadow: "0 0 20px rgba(0, 212, 255, 0.66)",
165+
}}
166+
/>
167+
<div style={{ color: BRAND.sub, fontSize: 18 }}>Live data</div>
168+
</div>
169+
<div style={{ color: BRAND.fg, fontSize: 18 }}>domainstack.io</div>
170+
</div>
171+
</div>
172+
</div>,
173+
{
174+
...size,
175+
fonts,
176+
},
177+
);
178+
}

app/opengraph-image.png

-17.3 KB
Binary file not shown.

app/opengraph-image.tsx

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { ImageResponse } from "next/og";
2+
import { LogoSimple } from "@/components/logo";
3+
import { BRAND, CHIPS, hexToRGBA, loadFontsForOG } from "@/lib/opengraph-image";
4+
5+
export const alt = "Domainstack — Domain Intelligence Made Easy";
6+
export const size = { width: 1200, height: 630 };
7+
export const contentType = "image/png";
8+
9+
export default async function OGImage() {
10+
const fonts = await loadFontsForOG();
11+
12+
return new ImageResponse(
13+
<div
14+
style={{
15+
width: "100%",
16+
height: "100%",
17+
display: "flex",
18+
position: "relative",
19+
background: BRAND.bg,
20+
fontFamily: "Geist", // must match fonts[].name
21+
}}
22+
>
23+
{/* Ambient glows */}
24+
<div
25+
style={{
26+
position: "absolute",
27+
inset: "-10%",
28+
backgroundColor: "transparent",
29+
backgroundImage: [
30+
`radial-gradient(900px 520px at 12% 18%, rgba(124,92,252,0.28) 0%, rgba(0,0,0,0) 60%)`,
31+
`radial-gradient(1000px 560px at 86% 24%, rgba(0,212,255,0.24) 0%, rgba(0,0,0,0) 62%)`,
32+
`radial-gradient(800px 520px at 50% 112%, rgba(34,197,94,0.20) 0%, rgba(0,0,0,0) 62%)`,
33+
`radial-gradient(1400px 700px at 50% 50%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.35) 100%)`,
34+
].join(","),
35+
}}
36+
/>
37+
38+
{/* Content */}
39+
<div
40+
style={{
41+
display: "flex",
42+
flexDirection: "column",
43+
justifyContent: "space-between",
44+
width: "100%",
45+
height: "100%",
46+
padding: "56px 64px",
47+
}}
48+
>
49+
{/* Header */}
50+
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
51+
<LogoSimple
52+
width={40}
53+
height={40}
54+
style={{
55+
color: BRAND.fg,
56+
display: "block",
57+
borderRadius: 12,
58+
background: "linear-gradient(135deg, #1A1F27 0%, #0F1216 100%)",
59+
boxShadow:
60+
"0 8px 40px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.06)",
61+
padding: 8,
62+
}}
63+
/>
64+
<div style={{ display: "flex", flexDirection: "column" }}>
65+
<div
66+
style={{
67+
fontSize: 22,
68+
color: BRAND.fg,
69+
letterSpacing: 0.3,
70+
fontWeight: 600,
71+
}}
72+
>
73+
Domainstack
74+
</div>
75+
<div style={{ fontSize: 14, color: BRAND.sub }}>
76+
Domain Intelligence Made Easy
77+
</div>
78+
</div>
79+
</div>
80+
81+
{/* Title + subtitle + chips */}
82+
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
83+
<div
84+
style={{
85+
fontSize: 35,
86+
lineHeight: 1.5,
87+
color: BRAND.fg,
88+
maxWidth: 1000,
89+
}}
90+
>
91+
Domainstack unlocks full domain intelligence in one click:
92+
</div>
93+
94+
<div
95+
style={{ display: "flex", flexWrap: "wrap", gap: 10, marginTop: 6 }}
96+
>
97+
{CHIPS.map(({ label, color }) => (
98+
<div
99+
key={label}
100+
style={{
101+
fontSize: 20,
102+
color: BRAND.fg,
103+
padding: "10px 16px",
104+
borderRadius: 999,
105+
background: `linear-gradient(180deg, ${hexToRGBA(color, 0.1)} 0%, rgba(255,255,255,0.03) 100%)`,
106+
border: `1px solid ${hexToRGBA(color, 0.28)}`,
107+
boxShadow:
108+
"0 1px 0 rgba(255,255,255,0.05) inset, 0 8px 24px rgba(0,0,0,0.35)",
109+
}}
110+
>
111+
{label}
112+
</div>
113+
))}
114+
</div>
115+
</div>
116+
117+
{/* Footer */}
118+
<div
119+
style={{
120+
display: "flex",
121+
alignItems: "center",
122+
justifyContent: "space-between",
123+
borderTop: "1px solid rgba(255,255,255,0.08)",
124+
paddingTop: 18,
125+
}}
126+
>
127+
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
128+
<div
129+
style={{
130+
width: 10,
131+
height: 10,
132+
borderRadius: 999,
133+
background: "rgba(0, 212, 255, 1)",
134+
boxShadow: "0 0 20px rgba(0, 212, 255, 0.66)",
135+
}}
136+
/>
137+
<div style={{ color: BRAND.sub, fontSize: 18 }}>Live data</div>
138+
</div>
139+
<div style={{ color: BRAND.fg, fontSize: 18 }}>domainstack.io</div>
140+
</div>
141+
</div>
142+
</div>,
143+
{
144+
...size,
145+
fonts,
146+
},
147+
);
148+
}

components/logo.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,20 @@ export function Logo({ title, ...props }: LogoProps) {
1818
);
1919
}
2020

21-
export default Logo;
21+
export function LogoSimple({ title, ...props }: LogoProps) {
22+
return (
23+
<svg
24+
xmlns="http://www.w3.org/2000/svg"
25+
viewBox="0 0 512 512"
26+
role="img"
27+
fill="currentColor"
28+
aria-label={title}
29+
{...props}
30+
>
31+
<path
32+
fill="currentColor"
33+
d="M273.04,470.6c-8.73-.09-17.66-.45-26.49-1.07-137.38-9.68-213.38-67.84-216.54-70.31-6.17-4.83-9.71-12.11-9.71-19.98v-216.47c0-11.15,7.14-20.94,17.76-24.37,1.45-.46,2.75-.69,3.96-.69,5.98,0,15.23,6.19,26.93,14.03l3.15,2.11c6.76,6,22.06,17.39,49.74,27.72,36.7,13.69,81.85,20.64,134.18,20.64,68.38,0,110.84-8.69,137.66-28.17,7.8-5.67,12.42-14.82,12.35-24.45-.07-9.64-4.8-18.72-12.67-24.28-31.04-21.97-77.25-33.1-137.35-33.1-6.82,0-13.23-2.68-18.05-7.54-4.8-4.86-7.41-11.3-7.34-18.13.14-13.85,11.92-25.12,26.26-25.12,45.84.08,85.92,6.37,119.13,18.7,26.07,9.68,47.97,23.09,65.08,39.86,30.63,30.01,33.86,39.68,34.76,57.79.35,7.06-.96,12.36-5.19,21.02-1.44,2.47-16.85,20.38-49.08,37.89-44.43,24.13-100.14,36.37-165.58,36.37s-140.06-20.37-148.63-22.7l-36.28-9.66v145.69l15.27,8.09c26.71,14.15,83.51,38.73,163.75,44.38,9.01.63,18.16.95,27.18.95,104.15,0,171.62-41.45,175.35-43.79h0c4.05-2.55,8.75-3.89,13.57-3.89,8.78,0,16.81,4.36,21.48,11.67,7.49,11.72,4.16,27.36-7.43,34.86-3.27,2.13-81.42,51.97-202.83,51.97h-4.41Z"
34+
/>
35+
</svg>
36+
);
37+
}

0 commit comments

Comments
 (0)