feat: share image, metadata and icons that match the new site
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user