Files
portifolio-evandrodevbr/lib/og-card.tsx
T
Evandro Fonseca Junior e80f73d075 feat(cases): mídia no topo, títulos de busca curtos e CreativeWork
Case com mídia ao lado do título e play acessível; problema de cada cliente reescrito com base nos dados; serviços relacionados em 2 cards; FAQ antes do próximo passo e bloco 'Onde já apliquei isso'; Apolo citada em IoT e arquitetura. metaTitle até 43 caracteres e descrições até 155 com teste; JSON-LD CreativeWork; imagens de compartilhamento abaixo de 100 KB; 404 e robots corrigidos; server-only nos módulos com node:.
2026-10-14 14:15:25 -03:00

140 lines
3.9 KiB
TypeScript

import "server-only";
import { readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { getOgImagePath, PROJECT_VIDEO_IDS } from "./project-showcase";
const brandMark = `data:image/png;base64,${readFileSync(
join(process.cwd(), "public/brand/mark-dark.png"),
).toString("base64")}`;
export const OG_SIZE = { width: 1200, height: 630 } as const;
export const OG_CONTENT_TYPE = "image/png";
export const OG_PHOTO_CONTENT_TYPE = "image/jpeg";
/** Teto de uma imagem de compartilhamento: quem recebe o link baixa a imagem a cada prévia, então ela precisa ser leve. */
export const OG_IMAGE_MAX_BYTES = 150_000;
/**
* Capa 1200x630 gravada da página do projeto (public/videos/<id>/og.jpg), usada como está na imagem de compartilhamento.
* `undefined` se não existir ou se pesar o teto ou mais: nesse caso quem chama gera o cartão simples, sem imagem de fundo.
*/
export function readProjectOgImage(id: string): Buffer | undefined {
if (!PROJECT_VIDEO_IDS.includes(id)) return undefined;
const path = join(process.cwd(), "public", getOgImagePath(id));
try {
if (statSync(path).size >= OG_IMAGE_MAX_BYTES) return undefined;
return readFileSync(path);
} catch {
return undefined;
}
}
export function OgCard({
eyebrow,
title,
description,
backgroundImage,
}: {
eyebrow: string;
title: string;
description: string;
/** Imagem de fundo (data URI), escurecida da esquerda para a direita para o texto continuar legível. */
backgroundImage?: string;
}) {
return (
<div
style={{
width: "100%",
height: "100%",
display: "flex",
position: "relative",
flexDirection: "column",
justifyContent: "space-between",
background: "#050505",
color: "#f4f1e8",
padding: 72,
fontFamily: "Arial, sans-serif",
}}
>
{backgroundImage && (
<>
{/* biome-ignore lint/performance/noImgElement: o ImageResponse (satori) só desenha <img> */}
<img
src={backgroundImage}
alt=""
width={OG_SIZE.width}
height={OG_SIZE.height}
style={{ position: "absolute", top: 0, left: 0 }}
/>
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: OG_SIZE.width,
height: OG_SIZE.height,
display: "flex",
background:
"linear-gradient(90deg, rgba(5,5,5,0.97) 0%, rgba(5,5,5,0.9) 48%, rgba(5,5,5,0.58) 100%)",
}}
/>
</>
)}
{/* biome-ignore lint/performance/noImgElement: o ImageResponse (satori) só desenha <img> */}
<img
src={brandMark}
alt="Evandro.dev.br"
width={80}
height={80}
style={{ position: "absolute", top: 48, right: 72 }}
/>
<div
style={{
display: "flex",
paddingRight: 112,
color: "#ccff00",
fontSize: 24,
letterSpacing: "0.16em",
textTransform: "uppercase",
}}
>
{eyebrow}
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 28 }}>
<div
style={{
display: "flex",
fontSize: 64,
fontWeight: 700,
lineHeight: 1.08,
}}
>
{title}
</div>
<div
style={{
display: "flex",
maxWidth: 980,
color: "#c7c3ba",
fontSize: 28,
lineHeight: 1.35,
}}
>
{description}
</div>
</div>
<div
style={{
display: "flex",
borderTop: "1px solid #343434",
paddingTop: 24,
fontSize: 20,
letterSpacing: "0.12em",
}}
>
EVANDRO.DEV.BR / ENGENHARIA DE SOFTWARE
</div>
</div>
);
}