feat: share image, metadata and icons that match the new site
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Successful in 6m28s
Build & Deploy / 🚀 Deploy (push) Successful in 17s
Build & Deploy / 🩺 Smoke Test (push) Successful in 4s
Build & Deploy / 🔔 Notify (push) Successful in 2s

The link preview used an old template with a blog description. The share
image is now a screenshot of the real hero (scripts/render-share-image.mjs),
served as Open Graph and Twitter image. Title and description come from one
place and are used by the page, the previews and the structured data. Adds an
apple touch icon and removes the old share-image template and its fonts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 23:33:02 +02:00
co-authored by Claude Opus 5.5
parent 007a691357
commit 123ab8e354
19 changed files with 128 additions and 362 deletions
-248
View File
@@ -1,248 +0,0 @@
import React from "react";
interface OGImageTemplateProps {
title: string;
description?: string;
label?: string;
accentColor?: string;
keyword?: string;
}
export function OGImageTemplate({
title,
description,
label,
accentColor,
keyword,
}: OGImageTemplateProps) {
const accent = accentColor || "#3b82f6";
const slateDark = "#0f172a";
const slateLight = "#64748b";
const containerStyle: React.CSSProperties = {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
backgroundColor: "#ffffff",
padding: "80px",
position: "relative",
fontFamily: "Inter",
};
return (
<div style={containerStyle}>
{/* Background Grid Pattern */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: `linear-gradient(rgba(148,163,184,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(148,163,184,0.06) 1px, transparent 1px)`,
backgroundSize: "40px 40px",
display: "flex",
}}
/>
{/* Accent geometric block (right side) */}
<div
style={{
position: "absolute",
top: "60px",
right: "80px",
width: "200px",
height: "200px",
borderRadius: "24px",
border: `3px solid ${accent}`,
opacity: 0.15,
display: "flex",
}}
/>
<div
style={{
position: "absolute",
top: "100px",
right: "120px",
width: "160px",
height: "160px",
borderRadius: "20px",
backgroundColor: accent,
opacity: 0.08,
display: "flex",
}}
/>
{/* Small accent circles */}
<div
style={{
position: "absolute",
top: "80px",
right: "100px",
width: "16px",
height: "16px",
borderRadius: "8px",
backgroundColor: accent,
opacity: 0.5,
display: "flex",
}}
/>
<div
style={{
position: "absolute",
top: "240px",
right: "90px",
width: "10px",
height: "10px",
borderRadius: "5px",
backgroundColor: accent,
opacity: 0.3,
display: "flex",
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
position: "relative",
zIndex: 10,
maxWidth: "850px",
}}
>
{/* Label / Category */}
{label && (
<div
style={{
fontSize: "18px",
fontWeight: 700,
color: accent,
textTransform: "uppercase",
letterSpacing: "0.2em",
marginBottom: "24px",
display: "flex",
}}
>
{label}
</div>
)}
{/* Title */}
<div
style={{
fontSize: title.length > 40 ? "56px" : "72px",
fontWeight: 700,
color: slateDark,
lineHeight: "1.1",
marginBottom: "28px",
display: "flex",
letterSpacing: "-0.03em",
}}
>
{title}
</div>
{/* Description */}
{description && (
<div
style={{
fontSize: "26px",
color: slateLight,
lineHeight: "1.45",
display: "flex",
fontWeight: 400,
}}
>
{description.length > 120
? description.substring(0, 117) + "..."
: description}
</div>
)}
</div>
{/* Brand Footer */}
<div
style={{
position: "absolute",
bottom: "60px",
left: "80px",
display: "flex",
alignItems: "center",
}}
>
<div
style={{
width: "50px",
height: "3px",
backgroundColor: slateDark,
borderRadius: "2px",
marginRight: "16px",
}}
/>
<div
style={{
fontSize: "20px",
fontWeight: 700,
color: slateDark,
textTransform: "lowercase",
letterSpacing: "0.05em",
display: "flex",
}}
>
mintel.me
</div>
</div>
{/* Keyword badge (bottom-right) */}
{keyword && (
<div
style={{
position: "absolute",
bottom: "60px",
right: "80px",
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<div
style={{
width: "8px",
height: "8px",
borderRadius: "4px",
backgroundColor: accent,
display: "flex",
}}
/>
<div
style={{
fontSize: "14px",
fontWeight: 700,
color: "#94a3b8",
textTransform: "uppercase",
letterSpacing: "0.15em",
fontFamily: "ui-monospace, monospace",
display: "flex",
}}
>
{keyword}
</div>
</div>
)}
{/* Accent Strip */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "5px",
backgroundColor: accent,
}}
/>
</div>
);
}
+28 -4
View File
@@ -1,13 +1,37 @@
import { SITE_NAME, SITE_URL } from "@/src/content/site";
import {
OWNER,
SITE_DESCRIPTION,
SITE_NAME,
SITE_URL,
} from "@/src/content/site";
const STRUCTURED_DATA = {
"@context": "https://schema.org",
"@type": "ProfessionalService",
name: SITE_NAME,
url: SITE_URL,
description:
"Senior freelance developer for web apps, interfaces and websites: weekly capacity for product teams, making unreliable apps dependable, and websites built end to end.",
founder: { "@type": "Person", name: SITE_NAME },
description: SITE_DESCRIPTION,
image: `${SITE_URL}/marc-mintel.png`,
address: {
"@type": "PostalAddress",
streetAddress: OWNER.street,
postalCode: OWNER.city.split(" ")[0],
addressLocality: OWNER.city.split(" ").slice(1).join(" "),
addressCountry: "DE",
},
founder: {
"@type": "Person",
name: SITE_NAME,
jobTitle: "Freelance developer",
url: SITE_URL,
},
knowsAbout: [
"Web applications",
"User interfaces",
"Websites",
"Process automation",
"Web3 frontends",
],
} as const;
/** Serialized server-side from constants only; "<" is escaped to keep it inert inside a script tag. */