feat(C208): artes da Rádio Jorge Solla 1313 — logo, capa e website card (Closes #1243)
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions

This commit is contained in:
Francisco Solla 2026-09-21 12:43:02 -03:00
parent 8a5e271f6d
commit 77edfc191f
14 changed files with 447 additions and 1 deletions

4
.gitignore vendored
View file

@ -111,6 +111,10 @@ next-env.d.ts
# workstation, nunca versionado (o deploy npm não o toca).
/scripts/reels/.venv/
# C208 — artes da Rádio Jorge Solla 1313 (zeno.fm): o que é versionado mora em
# public/campaign-kit/radio/; aqui ficam só rascunhos/previews locais.
/data/radio-artes/
# Local-only sensitive material (agents may read; never commit content)
/private/**
!/private/README.md

View file

@ -0,0 +1 @@
**C208 — Artes da Rádio Jorge Solla 1313: logo escolhida, capa e website card (2026-09-21):** a estação no zeno.fm (que estava com os três campos de imagem ocupados pela arte da **carreata** eventual) ganha identidade geral, decidida com o humano em conversa e agora registrada no repo. Os ativos versionados vivem em `public/campaign-kit/radio/` e são reproduzidos por `scripts/build-radio-artes.mjs`: **logo** = o "O" do SOLLA da marca positiva em fundo branco (recorte do artboard oficial `public/Prancheta 1@3x.png` — letra com a estrela vazada e o rastro verde/amarelo/azul — em PNG opaco para a estrela seguir branca em qualquer fundo; o zeno exibe o logo em círculo de 152²), **capa** = `RÁDIO` (Brexter Bold) + **marca vertical** oficial (o "1313" embaixo do JORGE SOLLA, com o slogan) + o candidato à direita em conjunto centralizado (ilustração como padrão, `capa-com-foto.png` como alternativa), e **station website card** em 1200×720 no mesmo sistema de fundo branco (nome + assinatura). O contrato de cada campo foi medido na própria página da estação e virou guarda do builder (falha fechado): a capa 2048×1024 tem de manter todo o conteúdo na **faixa segura central 2048×512** (no desktop o zeno corta 4:1) e deixar branca a área do logo circular que ele sobrepõe no canto inferior esquerdo (x 60..380, y ≥700) — a ilustração (500 px) e a marca entram inteiras em y 262..762; o card é exibido em quadrado (`aspect-square`), então tudo vive no quadrado central 720×720. A **ilustração em alta** (`radio/ilustracao-solla-1313.png`, 769×1122 RGBA) sai da ilustração do card de foto de perfil (`public/cards/photo-portrait-frame.png`, que só existia a ~255 px): recorte nativo, 4× por IA (Real-ESRGAN ncnn-vulkan, modelo `realesrgan-x4plus-anime`) e remoção de fundo por flood fill com abertura morfológica (a faixa do frame é o vermelho legado `#b72031`, não a cor do kit) — receita no cabeçalho do builder. As **logos finalistas** ficam em `radio/finalistas/` (arquivo da decisão): "1313" no vermelho, marca completa positiva no branco e o "O" branco no vermelho. O mapa de uso entra na seção nova do `public/campaign-kit/README.md` (a marca continua com dono único no README + manual; nada de recópia de paleta) e o builder depende de Brexter e Arimo no fontconfig local, com instruções no cabeçalho. Sem UI do app, sem migration, sem Consent/access, sem URL pública nova — são assets estáticos + um builder de script.

View file

@ -0,0 +1,103 @@
# Artes da Rádio Jorge Solla 1313 (zeno.fm) — logo, capa e website card
Status: entregue (2026-09-21)
Atualizado em: 2026-09-21 — as-built: ativos versionados em `public/campaign-kit/radio/` (logo, capa, website card, ilustração em alta e as três logos finalistas), gerados por `scripts/build-radio-artes.mjs`; mapa de uso no `public/campaign-kit/README.md`.
Issue: #1241 (C208) · Design UI: N/A — peças estáticas aprovadas pelo humano no fluxo (não é superfície de app)
Impeccable: A — assets e builder; nenhuma UI do app tocada
Appetite: ~0,5 dia eng; 3 ativos + finalistas + builder; sem migration
Responsável: —
## Intenção
A estação [`Rádio Jorge Solla 1313`](https://zeno.fm/radio/jorge-solla-1313/) no
zeno.fm estava com os três campos de imagem ocupados pela arte da **carreata**
(eventual). O humano pediu artes **gerais** da rádio — logo, capa e station
website card — para o painel do zeno, com a marca oficial do kit 1313, e depois
decidiu o sistema visual em conversa: a **logo escolhida é o "O" do SOLLA**
(letra oficial com a estrela vazada e o rastro verde/amarelo/azul) em fundo
branco; a capa usa a marca vertical ("1313" embaixo do "JORGE SOLLA", com o
slogan) centrada com o candidato à direita; o card segue o mesmo fundo branco.
Esta entrega **registra** no repo os ativos aprovados, as logos finalistas, a
ilustração do candidato em alta e o builder que os reproduz.
## Persona e fluxo
- **Persona / contexto:** quem opera a estação no painel do zeno (comunicação) e
quem produz peças da campanha (agentes de design, reels, dossiês).
- **Job principal:** achar o ativo oficial certo e saber o contrato de cada
campo do zeno (corte circular do logo, faixa segura da capa, corte quadrado do
card) sem adivinhar.
- **Fluxo desejado:** abre o README do kit → acha a seção da rádio → pega o
arquivo do campo → sobe no painel. Se precisar mudar, roda o builder.
## Contrato do zeno (medido na página da estação)
| Campo | Arquivo | Como o zeno exibe |
| -------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Logo | `radio/radio-jorge-solla-1313-logo.png` (1024², fundo branco opaco) | círculo pequeno (CDN entrega 152², `rounded-full`); o desenho tem de ler dentro do círculo e em ~40 px |
| Cover Image | `radio/radio-jorge-solla-1313-capa.png` (2048×1024) | 2:1 no celular, 3:1 e 4:1 no desktop (`object-cover` central) → todo o conteúdo na **faixa segura central 2048×512**; o logo circular sobreposto no desktop (x 60..380, y ≥700) tem de achar a área branca |
| Station Website Card | `radio/radio-jorge-solla-1313-website-card.png` (1200×720) | quadrado (`aspect-square`, corte central 720×720) → o conteúdo vive nesse quadrado |
- Limites de arquivo: logo ≥500² e ≤5 MB; capa ≥2048×1024 e ≤5 MB; card ≥500×300
e ≤500 KB. Todos os três ficam muito abaixo (69 KB / 325 KB / 64 KB).
- O builder **revalida** os contratos (faixa segura, área do logo do zeno e
quadrado central do card) e falha fechado se algo sair do lugar.
## Decisões (2026-09-21)
- **Logo:** o "O" do SOLLA da marca positiva em fundo branco — recorte do
artboard oficial `public/Prancheta 1@3x.png` (mesma paleta do kit:
`#e4102f`/`#184e92`/`#009647`/`#ffeb00` conferidos), em PNG opaco para a
estrela continuar branca sobre qualquer fundo. Finalistas arquivadas em
`radio/finalistas/`: "1313" no vermelho (`numero-negativo`), marca completa
positiva no branco e o "O" branco no vermelho.
- **Capa:** `RÁDIO` (Brexter Bold, vermelho) + marca vertical + candidato à
direita, em conjunto centralizado; a ilustração (padrão) e a foto
(`capa-com-foto.png`) entram inteiras na faixa segura (y 262..762).
- **Sem repetir símbolo:** onde o "JORGE SOLLA" estilizado já está, não entra o
"O" como badge — a estrela já vive dentro do próprio lockup.
- **Ilustração em alta:** a do card de foto de perfil
(`public/cards/photo-portrait-frame.png`) só existia a ~255 px; virou
769×1122 por recorte + 4× por IA (Real-ESRGAN ncnn-vulkan, modelo
`realesrgan-x4plus-anime`) + remoção de fundo por flood fill + abertura
morfológica (a faixa do frame é `#b72031`, não a cor do kit). Receita no
cabeçalho do builder; a ilustração final é entrada versionada.
## Objetivo e aceite
- `public/campaign-kit/radio/` com logo, capa, website card, `capa-com-foto.png`
(alternativa) e `ilustracao-solla-1313.png` (alta, RGBA), mais as três
finalistas em `radio/finalistas/`.
- `public/campaign-kit/README.md` com a seção da rádio (mapa de uso + contrato
dos campos + como regenerar) — a marca continua com dono único.
- `scripts/build-radio-artes.mjs` reproduz o conjunto a partir dos ativos do kit
e falha fechado fora do contrato; depende de Brexter e Arimo instalados no
fontconfig local (instruções no cabeçalho).
- ONE entrada em `docs/changelog/2026-09-21-c208.md`.
## Fora de escopo
- Trocar a arte no painel do zeno (é upload humano; a entrega é o arquivo).
- Player próprio da rádio na home (S25, Issue #1239) e qualquer UI do app.
- Redesenhar os ativos oficiais do kit ou o card de foto de perfil.
- Versionar os intermediários do upscale por IA (o `.4x` fica local; reprodutível
pelo comando documentado).
## Rabbit holes
- **Segundo mapa de uso da marca.** A seção da rádio descreve o contrato dos
campos; as regras de marca continuam só no README do kit + manual. **Corte:**
sem recópia de paleta/regra.
- **Segunda fonte da ilustração.** Guardar o `.4x` gigante no repo para "não
precisar da IA". **Corte:** a entrada é o PNG final; a receita é o comando.
- **Arte por campo com nomes próprios.** **Corte:** nomes espelham o campo do
zeno (`logo`, `capa`, `website-card`), sem sinônimos.
## Referências
- GitHub Issue: #1241 (C208)
- `public/campaign-kit/README.md` (mapa de uso; dona da marca) e
`docs/campaign-kit/manual-campanha-jorge-solla-1313.pdf`
- `scripts/build-radio-artes.mjs` (builder + receita da ilustração)
- S21/S22/S24 (`docs/plans/jingles-*.md`, `docs/plans/radio-embed-direto.md`) —
a rádio no site; aqui é a identidade da estação fora do site

View file

@ -21,7 +21,7 @@
"src/components/ui/marker.tsx",
"src/components/ui/message.tsx"
],
"ignoreBinaries": ["opencode", "ffmpeg", "ffprobe"],
"ignoreBinaries": ["opencode", "ffmpeg", "ffprobe", "fc-match"],
"ignoreDependencies": [
"tailwindcss-animate",
"tw-animate-css",

View file

@ -76,3 +76,35 @@ manual; as entradas abaixo descrevem a forma (uso a confirmar).
não é uma quarta mensagem nem headline solta.
- O nome e o número são os elementos de memorização: não recriar lockups
tipográficos quando o ativo oficial existe.
## Rádio Jorge Solla 1313 (zeno.fm)
Artes da estação [`zeno.fm/radio/jorge-solla-1313`](https://zeno.fm/radio/jorge-solla-1313/),
geradas por [`scripts/build-radio-artes.mjs`](../../scripts/build-radio-artes.mjs)
a partir dos ativos deste kit e dos artboards de alta em `public/`. Os campos do
painel do zeno têm contratos diferentes — medidos na própria página da estação:
| Arquivo | Campo | Como o zeno exibe |
| --- | --- | --- |
| `radio/radio-jorge-solla-1313-logo.png` (1024², fundo branco opaco) | Logo | círculo pequeno (CDN entrega 152², `rounded-full`) — o desenho tem de ler dentro do círculo e em ~40 px |
| `radio/radio-jorge-solla-1313-capa.png` (2048×1024) | Cover Image | 2:1 no celular, 3:1 e 4:1 no desktop (`object-cover` central) → todo o conteúdo na **faixa segura central 2048×512**; a área do logo circular sobreposto no desktop (x 60..380, y ≥700) fica branca |
| `radio/radio-jorge-solla-1313-website-card.png` (1200×720) | Station Website Card | quadrado (`aspect-square`, corte central 720×720) → o conteúdo vive nesse quadrado |
- **Logo escolhida (2026-09-21):** o "O" do SOLLA da marca positiva em fundo
branco — recorte do artboard `public/Prancheta 1@3x.png`, com a estrela vazada
e o rastro verde/amarelo/azul. As finalistas (arquivo da decisão) ficam em
`radio/finalistas/`: "1313" no vermelho, marca completa positiva no branco e o
"O" branco no vermelho.
- **Capa:** `RÁDIO` (Brexter Bold) + marca vertical oficial (o "1313" embaixo do
JORGE SOLLA, com o slogan) + o candidato à direita, em conjunto centralizado —
ilustração (padrão) ou foto (`radio/capa-com-foto.png`, alternativa).
- **Ilustração em alta:** `radio/ilustracao-solla-1313.png` (769×1122, RGBA)
parte da ilustração do card de foto de perfil
(`cards/photo-portrait-frame.png`, recorte em x 100..355, y 840..1265),
sobe 4× por IA (Real-ESRGAN ncnn-vulkan, modelo `realesrgan-x4plus-anime`) e
perde o fundo por flood fill + abertura morfológica (a faixa do frame é
`#b72031`, não a cor do kit). A receita completa está no cabeçalho do builder.
- **Regenerar:** `node scripts/build-radio-artes.mjs` — exige Brexter e Arimo no
fontconfig local (instruções no cabeçalho do script). O builder revalida o
contrato da capa e do card (faixa segura, área do logo do zeno, quadrado
central) e falha fechado.

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -0,0 +1,306 @@
/**
* Artes da Rádio Jorge Solla 1313 — `public/campaign-kit/radio/`.
*
* Gera (e revalida) os ativos da estação no zeno.fm a partir dos ativos
* oficiais do kit 1313 e dos artboards de alta em `public/`:
*
* radio-jorge-solla-1313-logo.png 1024x1024 logo escolhida (o "O"
* do SOLLA em fundo branco;
* o zeno exibe em círculo)
* radio-jorge-solla-1313-capa.png 2048x1024 capa da estação (crop
* 4:1 no desktop = a faixa
* segura 2048x512 central)
* radio-jorge-solla-1313-website-card.png 1200x720 station website card
* (exibido em quadrado)
* capa-com-foto.png 2048x1024 alternativa da capa com
* a foto oficial
* finalistas/logo-finalista-*.png 1024x1024 logos finalistas
* (arquivo da decisão)
* ilustracao-solla-1313.png 769x1122 ilustração em alta
* (entrada versionada)
*
* Regras de marca: `public/campaign-kit/README.md` (paleta #e4102f/#184e92, marca
* positiva/oficial para fundo claro, sem recriar lockup). "RÁDIO" usa Brexter
* Bold (display face oficial, `src/app/(frontend)/fonts`) e a assinatura usa
* Arimo (face dos cards, via next/font). Prerequisito local (fora do repo):
*
* cp src/app/'(frontend)'/fonts/Brexter-Bold.ttf ~/.local/share/fonts/
* cp <repo>/.next/static/media/<arimo-latin>.woff2 ~/.local/share/fonts/Arimo-Regular.woff2
* fc-cache -f ~/.local/share/fonts
*
* Uso: node scripts/build-radio-artes.mjs
*/
import { execFileSync } from 'node:child_process'
import { statSync } from 'node:fs'
import { mkdir } from 'node:fs/promises'
import { createRequire } from 'node:module'
const require = createRequire(new URL('../package.json', import.meta.url))
const sharp = require('sharp')
const REPO = new URL('../', import.meta.url).pathname
const KIT = `${REPO}public/campaign-kit`
const OUT = `${KIT}/radio`
const FINALISTS = `${OUT}/finalistas`
const RED = '#e4102f'
const BLUE = '#184e92'
const WHITE = '#ffffff'
const SAFE_TOP = 256
const SAFE_BOTTOM = 768
const OVERLAY = { left: 60, right: 380, top: 700 } // logo circular do zeno (desktop)
const assertFont = (family) => {
const matched = execFileSync('fc-match', ['--format', '%{family}', `${family}:bold`], {
encoding: 'utf8',
})
if (!matched.toLowerCase().includes(family.toLowerCase())) {
console.error(`Fonte "${family}" não está instalada no fontconfig local — veja o cabeçalho.`)
process.exit(1)
}
}
async function text({ str, font = 'Brexter', size, fill, tracking = 0, weight = 700 }) {
const w = Math.ceil(str.length * size * 1.7 + 4 * tracking + 240)
const h = Math.ceil(size * 3)
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">
<text x="${w / 2}" y="${h / 2}" text-anchor="middle" dominant-baseline="central"
font-family="${font}" font-size="${size}" font-weight="${weight}" fill="${fill}"
letter-spacing="${tracking}">${str}</text></svg>`
const t = await sharp(Buffer.from(svg))
.png()
.trim({ threshold: 1 })
.png()
.toBuffer({ resolveWithObject: true })
return { input: t.data, w: t.info.width, h: t.info.height }
}
async function asset(file, width) {
const t = await sharp(file)
.trim({ threshold: 5 })
.resize({ width })
.png()
.toBuffer({ resolveWithObject: true })
return { input: t.data, w: t.info.width, h: t.info.height }
}
/** "O" do SOLLA recortado do artboard de alta (caixa medida no trim de 1654x1245). */
async function oMonogram(file, width) {
const trim = await sharp(file).trim({ threshold: 5 }).png().toBuffer({ resolveWithObject: true })
const glyph = await sharp(trim.data)
.extract({ left: 349, top: 258, width: 361, height: 330 })
.png()
.toBuffer()
const t = await sharp(glyph).resize({ width }).png().toBuffer({ resolveWithObject: true })
return { input: t.data, w: t.info.width, h: t.info.height }
}
/** Marca vertical oficial (nome, 1313 embaixo e slogan). */
async function verticalMark(height) {
const trim = await sharp(`${REPO}public/Prancheta 1@3x.png`)
.trim({ threshold: 5 })
.png()
.toBuffer({ resolveWithObject: true })
const t = await sharp(trim.data).resize({ height }).png().toBuffer({ resolveWithObject: true })
return { input: t.data, w: t.info.width, h: t.info.height }
}
/** Foto oficial do candidato (cutout com alfa). */
async function candidatePhoto(height) {
const trim = await sharp(`${REPO}public/JOA00162.avif`)
.trim({ threshold: 5 })
.png()
.toBuffer({ resolveWithObject: true })
const t = await sharp(trim.data).resize({ height }).png().toBuffer({ resolveWithObject: true })
return { input: t.data, w: t.info.width, h: t.info.height }
}
/** Ilustração em alta (recorte + upscale por IA + fundo removido — ver README do kit). */
async function illustration(height) {
const t = await sharp(`${OUT}/ilustracao-solla-1313.png`)
.trim({ threshold: 1 })
.resize({ height })
.png()
.toBuffer({ resolveWithObject: true })
return { input: t.data, w: t.info.width, h: t.info.height }
}
const place = (layers) =>
layers.map(({ asset: a, x, y, cx, cy }) => ({
input: a.input,
left: Math.round(x ?? cx - a.w / 2),
top: Math.round(y ?? cy - a.h / 2),
}))
async function render({ name, width, height, layers, bg = WHITE }) {
const buffer = await sharp({ create: { width, height, channels: 4, background: bg } })
.composite(place(layers))
.png({ compressionLevel: 9 })
.toBuffer()
await sharp(buffer).toFile(`${OUT}/${name}`)
console.log(
name,
`${width}x${height}`,
`${(statSync(`${OUT}/${name}`).size / 1024).toFixed(0)}KB`,
)
return buffer
}
/** Faixa segura da capa inteira branca fora de y 256..768, e livre do logo do zeno. */
async function assertCoverSafe(buffer) {
const { data, info } = await sharp(buffer).raw().toBuffer({ resolveWithObject: true })
const white = (i) => data[i] > 250 && data[i + 1] > 250 && data[i + 2] > 250
let outside = 0
let overlay = 0
for (let y = 0; y < info.height; y++) {
for (let x = 0; x < info.width; x++) {
const i = (y * info.width + x) * 4
if (white(i)) continue
if (y < SAFE_TOP || y >= SAFE_BOTTOM) outside++
if (x >= OVERLAY.left && x <= OVERLAY.right && y >= OVERLAY.top) overlay++
}
}
if (outside > 0 || overlay > 0) {
throw new Error(
`capa fora do contrato: ${outside}px fora da faixa segura, ${overlay}px no logo do zeno`,
)
}
}
/** Card: nada fora do quadrado central 720x720 (é o crop exibido no zeno). */
async function assertCardSquare(buffer) {
const { data, info } = await sharp(buffer).raw().toBuffer({ resolveWithObject: true })
const white = (i) => data[i] > 250 && data[i + 1] > 250 && data[i + 2] > 250
let outside = 0
for (let y = 0; y < info.height; y++) {
for (let x = 0; x < info.width; x++) {
if (x >= 240 && x < 960) continue
if (!white((y * info.width + x) * 4)) outside++
}
}
if (outside > 0) throw new Error(`card com ${outside}px fora do quadrado central`)
}
assertFont('Brexter')
assertFont('Arimo')
const ILLUSTRATION = `${OUT}/ilustracao-solla-1313.png`
try {
await sharp(ILLUSTRATION).metadata()
} catch {
console.error(
`Ilustração ausente: ${ILLUSTRATION} — o pipeline está documentado em public/campaign-kit/README.md.`,
)
process.exit(1)
}
await mkdir(FINALISTS, { recursive: true })
/* ------------------------------- Logo -------------------------------
* O "O" do SOLLA da marca positiva em fundo branco opaco — legível no círculo
* pequeno do zeno e sem repetir o símbolo onde o nome estilizado já está.
*/
await render({
name: 'radio-jorge-solla-1313-logo.png',
width: 1024,
height: 1024,
layers: [{ asset: await oMonogram(`${REPO}public/Prancheta 1@3x.png`, 580), cx: 512, cy: 512 }],
})
/* ------------------------------- Capa -------------------------------
* "RÁDIO" + marca vertical (nome, 1313 e slogan) + candidato à direita em
* conjunto centralizado; tudo dentro da faixa segura 2048x512.
*/
{
const KICKER_PX = 58
const KICKER_GAP = 14
const MARK_H = 420
const PERSON_H = 500
const PERSON_GAP = 60
const kicker = await text({ str: 'RÁDIO', size: KICKER_PX, fill: RED, tracking: 22 })
const mark = await verticalMark(MARK_H)
const top = 512 - (kicker.h + KICKER_GAP + mark.h) / 2
for (const [name, person] of [
['radio-jorge-solla-1313-capa.png', await illustration(PERSON_H)],
['capa-com-foto.png', await candidatePhoto(PERSON_H)],
]) {
const groupW = mark.w + PERSON_GAP + person.w
const markX = Math.round((2048 - groupW) / 2)
const buffer = await render({
name,
width: 2048,
height: 1024,
layers: [
{ asset: kicker, cx: markX + mark.w / 2, cy: top + kicker.h / 2 },
{ asset: mark, x: markX, y: top + kicker.h + KICKER_GAP },
{ asset: person, cx: markX + mark.w + PERSON_GAP + person.w / 2, cy: 512 },
],
})
await assertCoverSafe(buffer)
}
}
/* --------------------------- Website card ----------------------------
* 1200x720 (5:3) · o zeno exibe em quadrado (corte central 720x720).
*/
{
const kicker = await text({ str: 'RÁDIO', size: 52, fill: RED, tracking: 16 })
const mark = await asset(`${KIT}/jorge-solla-positivo.png`, 560)
const tag = await text({
str: 'A rádio da nossa caminhada pela Bahia.',
font: 'Arimo',
weight: 400,
size: 30,
fill: BLUE,
tracking: 1,
})
let y = 360 - (kicker.h + 14 + mark.h + 22 + tag.h) / 2
const layers = []
for (const [item, gap] of [
[kicker, 14],
[mark, 22],
[tag, 0],
]) {
layers.push({ asset: item, cx: 600, y })
y += item.h + gap
}
const buffer = await render({
name: 'radio-jorge-solla-1313-website-card.png',
width: 1200,
height: 720,
layers,
})
await assertCardSquare(buffer)
}
/* ----------------------------- Finalistas ----------------------------
* O arquivo da decisão de logo (2026-09-21): a escolhida é o "O" (acima).
*/
{
const numero = await asset(`${KIT}/numero-negativo.png`, 860)
await render({
name: 'finalistas/logo-finalista-1313-no-vermelho.png',
width: 1024,
height: 1024,
bg: RED,
layers: [{ asset: numero, cx: 512, cy: 512 }],
})
const completa = await asset(`${REPO}public/Prancheta 1@3x.png`, 840)
await render({
name: 'finalistas/logo-finalista-marca-completa-no-branco.png',
width: 1024,
height: 1024,
layers: [{ asset: completa, cx: 512, cy: 512 }],
})
const oBranco = await oMonogram(`${REPO}public/Prancheta 3@3x.png`, 580)
await render({
name: 'finalistas/logo-finalista-o-branco-no-vermelho.png',
width: 1024,
height: 1024,
bg: RED,
layers: [{ asset: oBranco, cx: 512, cy: 512 }],
})
}