feat(brand): social preview do repositório, com a fonte versionada

O repo estava com usesCustomOpenGraphImage=false: todo link compartilhado em X,
LinkedIn, WhatsApp, Slack ou Discord saía com a arte cinza genérica do GitHub.
Para um projeto que depende de descoberta, é o conserto de menor custo e retorno
mais imediato da lista.

A arte usa a identidade que já existe — paleta lida de app/globals.css e as
fontes da aplicação (Atkinson Hyperlegible + IBM Plex Mono). Nada de gradiente
roxo ou o visual genérico que qualquer gerador cospe: o card tem que parecer o
produto, senão quem clica chega em outro lugar.

O painel direito mostra o rastro de uma demanda atravessando o sistema e
terminando no follow-up. É a doutrina do sistema vivo como imagem em vez de
adjetivo — 'no lead dies unseen' é o argumento do produto, não uma frase de
efeito.

A FONTE (og-card.html) vai versionada junto, e é o ponto: card cuja origem se
perde vira arte que ninguém atualiza quando o posicionamento muda — aí ou
envelhece mentindo, ou é refeito do zero com outra identidade. docs/brand/README.md
traz o comando de regeração.

Aplicar é manual: Settings > General > Social preview. Não há endpoint público
na API do GitHub para upload dessa imagem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMEbgy5YWXZimXQAN7oRMm
This commit is contained in:
Rafael Melgaço
2026-07-27 13:53:43 -03:00
co-authored by Claude Opus 5
parent 94a5202cc6
commit 59eee853ee
3 changed files with 169 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# Marca
## Social preview (Open Graph)
`og-social-preview.png` — 1280×640, é a imagem que aparece quando um link do
repositório é compartilhado no X, LinkedIn, WhatsApp, Slack ou Discord.
**Como aplicar:** GitHub → Settings → General → *Social preview* → Upload.
Não existe endpoint público na API para isso; é upload pela interface.
**Como regerar** (depois de mudar posicionamento, chips ou paleta):
```bash
# edite docs/brand/og-card.html, depois:
node -e '
import("@playwright/test").then(async ({ chromium }) => {
const b = await chromium.launch();
const p = await b.newPage({ viewport: { width: 1280, height: 640 }, deviceScaleFactor: 2 });
await p.goto("file://" + process.cwd() + "/docs/brand/og-card.html", { waitUntil: "networkidle" });
await p.evaluate(() => document.fonts.ready);
await p.screenshot({ path: "docs/brand/og-social-preview.png" });
await b.close();
});'
```
A fonte fica versionada de propósito: card cuja origem se perde vira arte que
ninguém consegue atualizar quando o posicionamento muda — e aí ou envelhece
mentindo, ou é refeito do zero com outra identidade.
## Regras da arte
- Paleta lida de `app/globals.css` (creme `#faf9f6`, sage `#506d48`, texto
`#1c1a16`). O card usa a identidade real do produto, não uma criada para ele.
- Tipografia: Atkinson Hyperlegible (títulos) + IBM Plex Mono (rótulos), as
mesmas da aplicação.
- O painel direito é a doutrina do sistema vivo virando imagem: o rastro que uma
demanda deixa ao atravessar o sistema, terminando no follow-up — o mecanismo
anti-morte. É o argumento do produto mostrado, não adjetivado.
- Card de compartilhamento **sempre** carrega o nome do produto. Sem wordmark,
quem vê a imagem não sabe de quem ela é.
+129
View File
@@ -0,0 +1,129 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:wght@400;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
/* Paleta lida de app/globals.css — a arte usa a identidade real do produto,
não uma inventada para o card. */
:root {
--bg: #faf9f6;
--surface: #ffffff;
--text: #1c1a16;
--muted: #5d594f;
--border: #e7e3da;
--accent: #506d48;
--accent-300: #a4ba9a;
--accent-100: #e4ebe0;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1280px; height: 640px;
background: var(--bg);
font-family: "Atkinson Hyperlegible", ui-sans-serif, system-ui, sans-serif;
color: var(--text);
display: grid;
grid-template-columns: 1fr 420px;
overflow: hidden;
}
/* Faixa de acento no topo — âncora de marca sem ocupar área. */
.rule { position: absolute; top: 0; left: 0; right: 0; height: 6px; background: var(--accent); }
.left { padding: 74px 0 66px 78px; display: flex; flex-direction: column; justify-content: space-between; }
.wordmark {
font-size: 27px; font-weight: 700; letter-spacing: -.015em; margin-bottom: 26px;
display: inline-flex; align-items: center; gap: 11px;
}
.wordmark::before {
content: ""; width: 15px; height: 15px; border-radius: 4px;
background: var(--accent);
}
.eyebrow {
font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 17px; letter-spacing: .14em; text-transform: uppercase;
color: var(--muted);
}
h1 {
font-size: 63px; line-height: 1.06; font-weight: 700; letter-spacing: -.022em;
margin-top: 20px; max-width: 15ch;
}
h1 .accent { color: var(--accent); }
.sub {
margin-top: 22px; font-size: 25px; line-height: 1.42; color: var(--muted); max-width: 30ch;
}
.chips { display: flex; gap: 10px; flex-wrap: wrap; }
.chip {
font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 16px; padding: 9px 15px; border-radius: 999px;
border: 1px solid var(--border); background: var(--surface); color: var(--muted);
}
.chip.on { border-color: var(--accent-300); background: var(--accent-100); color: var(--accent); }
/* Painel direito: o "rastro" que uma demanda deixa ao atravessar o sistema.
É a doutrina do sistema vivo virando imagem, em vez de adjetivo na copy. */
.right { position: relative; border-left: 1px solid var(--border); background: var(--surface);
padding: 56px; display: flex; flex-direction: column; justify-content: center; }
.right-title { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 14px;
letter-spacing: .13em; text-transform: uppercase; color: var(--muted); margin-bottom: 26px; }
.trail { display: flex; flex-direction: column; gap: 0; }
.node { display: grid; grid-template-columns: 26px 1fr; gap: 16px; }
.rail { display: flex; flex-direction: column; align-items: center; }
.dot {
width: 13px; height: 13px; border-radius: 50%;
border: 2.5px solid var(--accent); background: var(--surface); flex: none; margin-top: 5px;
}
.dot.fill { background: var(--accent); }
.line { width: 2px; flex: 1; background: var(--border); min-height: 34px; }
.label { font-size: 19px; line-height: 1.3; padding-bottom: 12px; }
.label small {
display: block; font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 14px; color: var(--muted); margin-top: 3px;
}
</style>
</head>
<body>
<div class="rule"></div>
<div class="left">
<div>
<div class="wordmark">DeskcommCRM</div>
<div class="eyebrow">open source · self-hosted · mit</div>
<h1>The open-source AI&nbsp;Sales&nbsp;OS for <span class="accent">WhatsApp</span></h1>
<p class="sub">AI agents that answer, qualify and move deals — on a server you own.</p>
</div>
<div class="chips">
<span class="chip on">No paid tier</span>
<span class="chip">Multi-tenant RLS</span>
<span class="chip">One-command install</span>
<span class="chip">2&nbsp;GB VPS</span>
</div>
</div>
<div class="right">
<div class="right-title">one lead, one trail</div>
<div class="trail">
<div class="node">
<div class="rail"><div class="dot fill"></div><div class="line"></div></div>
<div class="label">Lead arrives on WhatsApp<small>inbound · 09:41</small></div>
</div>
<div class="node">
<div class="rail"><div class="dot fill"></div><div class="line"></div></div>
<div class="label">Agent qualifies and replies<small>7 guardrails · audited</small></div>
</div>
<div class="node">
<div class="rail"><div class="dot fill"></div><div class="line"></div></div>
<div class="label">Moved down the pipeline<small>activity recorded</small></div>
</div>
<div class="node">
<div class="rail"><div class="dot"></div></div>
<div class="label">Follow-up scheduled<small>no lead dies unseen</small></div>
</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB