feat(og): capa do projeto como fundo da imagem de compartilhamento dos cases

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Evandro Fonseca JuniorandClaude Sonnet 5.5 committed 2026-10-08 16:37:00 -03:00
1 parent 540fe9f87a
commit ec760330d4
3 files changed
+80 -2

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
import { render } from "@testing-library/react";
import { OgCard, readProjectOgBackground } from "./og-card";
describe("readProjectOgBackground", () => {
it("returns the recorded cover of a showcase project as a JPEG data URI", () => {
const uri = readProjectOgBackground("nutriscan");
expect(uri).toMatch(/^data:image\/jpeg;base64,/);
expect((uri ?? "").length).toBeGreaterThan(5_000);
});
it("returns undefined for projects without a recorded cover", () => {
expect(readProjectOgBackground("twins-lab")).toBeUndefined();
expect(readProjectOgBackground("../../etc/passwd")).toBeUndefined();
});
});
describe("OgCard", () => {
const props = { eyebrow: "Case", title: "Título", description: "Resumo." };
it("keeps the plain dark card when there is no background", () => {
const { container } = render(<OgCard {...props} />);
// só a marca
expect(container.querySelectorAll("img")).toHaveLength(1);
});
it("puts the project cover behind the text with a darkening gradient", () => {
const { container } = render(
<OgCard {...props} backgroundImage="data:image/jpeg;base64,AAAA" />,
);
const images = [...container.querySelectorAll("img")];
expect(images).toHaveLength(2);
expect(images[0]).toHaveAttribute("src", "data:image/jpeg;base64,AAAA");
expect(images[0]).toHaveAttribute("alt", "");
expect(container.innerHTML).toContain("linear-gradient");
});
});
+37 -1
View File
@@ -1,5 +1,6 @@
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } 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"),
@@ -8,14 +9,25 @@ const brandMark = `data:image/png;base64,${readFileSync(
export const OG_SIZE = { width: 1200, height: 630 } as const;
export const OG_CONTENT_TYPE = "image/png";
/** Capa 1200x630 gravada da página do projeto (public/videos/<id>/og.jpg), como data URI; `undefined` se não existir. */
export function readProjectOgBackground(id: string): string | undefined {
if (!PROJECT_VIDEO_IDS.includes(id)) return undefined;
const path = join(process.cwd(), "public", getOgImagePath(id));
if (!existsSync(path)) return undefined;
return `data:image/jpeg;base64,${readFileSync(path).toString("base64")}`;
}
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
@@ -32,6 +44,30 @@ export function OgCard({
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%)",
}}
/>
</>
)}
<img
src={brandMark}
alt="Evandro.dev.br"