feat(seo): add a generated 1200x630 OG image (Gitea #4)

Social-sharing previews had no image — layout.tsx's OG/Twitter tags
existed but pointed at nothing. Adds src/app/opengraph-image.tsx using
next/og's ImageResponse to render a branded card (paw mark, wordmark,
tagline) at request time instead of shipping a static asset, and
switches twitter.card to summary_large_image now that there's an
image for it to show.
This commit is contained in:
admin committed 2026-08-11 15:27:43 +02:00
1 parent efdf407642
commit e47eed3a9d
2 files changed
+106 -1

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@ export const metadata: Metadata = {
type: "website",
},
twitter: {
card: "summary",
card: "summary_large_image",
title: "PawFeed",
description: "The social platform where pets are the stars.",
},
+105
View File
@@ -0,0 +1,105 @@
import { ImageResponse } from "next/og";
export const alt = "PawFeed — the social platform where pets are the stars.";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
// Simple 1-big-pad + 4-toes paw print, built from plain positioned circles —
// Satori's CSS support doesn't need raw SVG for a shape this simple.
function PawMark({ scale = 1, opacity = 1 }: { scale?: number; opacity?: number }) {
const s = (n: number) => n * scale;
return (
<div style={{ position: "relative", width: s(340), height: s(340), opacity, display: "flex" }}>
<div
style={{
position: "absolute",
left: s(70),
top: s(140),
width: s(200),
height: s(170),
borderRadius: "50% 50% 45% 45%",
background: "#FFFFFF",
}}
/>
<div style={{ position: "absolute", left: s(30), top: s(30), width: s(90), height: s(110), borderRadius: "50%", background: "#FFFFFF" }} />
<div style={{ position: "absolute", left: s(140), top: s(0), width: s(90), height: s(115), borderRadius: "50%", background: "#FFFFFF" }} />
<div style={{ position: "absolute", left: s(250), top: s(30), width: s(90), height: s(110), borderRadius: "50%", background: "#FFFFFF" }} />
<div style={{ position: "absolute", left: s(-20), top: s(120), width: s(80), height: s(100), borderRadius: "50%", background: "#FFFFFF" }} />
</div>
);
}
export default function Image() {
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
background: "linear-gradient(135deg, #FB923C 0%, #F97316 45%, #C2410C 100%)",
position: "relative",
}}
>
{/* Decorative oversized paw mark bleeding off the right edge — depth/layering */}
<div style={{ position: "absolute", right: -60, top: -40, display: "flex" }}>
<PawMark scale={2.05} opacity={0.16} />
</div>
{/* Content column */}
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
paddingLeft: 96,
paddingRight: 96,
width: "100%",
height: "100%",
}}
>
<div style={{ display: "flex", alignItems: "center", marginBottom: 28 }}>
<PawMark scale={0.62} />
</div>
<div
style={{
display: "flex",
fontSize: 132,
fontWeight: 800,
color: "#FFFFFF",
letterSpacing: -3,
lineHeight: 1,
}}
>
PawFeed
</div>
<div
style={{
display: "flex",
fontSize: 40,
fontWeight: 500,
color: "#FFF7ED",
marginTop: 22,
maxWidth: 820,
}}
>
Where pets are the stars.
</div>
<div
style={{
display: "flex",
fontSize: 26,
fontWeight: 600,
color: "#FFEDD5",
marginTop: 44,
letterSpacing: 1,
}}
>
pawfeed.org
</div>
</div>
</div>
),
{ ...size }
);
}