feat(S25): logo oficial da rádio no player — NEEDS ASSET do gate fechado (Related #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 13:10:56 -03:00
parent bb550cebff
commit 65bc0da0f7
3 changed files with 8 additions and 8 deletions

View file

@ -1 +1 @@
**S25 — Player próprio da Rádio 1313 na home (2026-09-21):** o iframe do zeno.fm que o S24 montou na home sai de cena e a Rádio Jorge Solla 1313 passa a tocar num **player próprio** — `<audio preload="none">` apontando direto para o stream (`stream.zeno.fm/hys86kx6k16tv`), com a cara da campanha e **zero request a terceiros antes do gesto de play** (o ganho colateral do S24 revertido para melhor: o pageview da home não fala mais com GA/rede de anúncio do widget). Sucessora explícita do S24 (#1235), que reverse de propósito os anti-goals dele ("não vira player custom") com nova evidência de produto registrada em `docs/plans/radio-player-proprio.md` (plano do item `done` não é reaberto). O contrato da rádio vive em `src/lib/radio.ts` (stream, página pública, título fixo "Rádio Jorge Solla 1313" = `icy-name`, mensagem literal `Ouça a Rádio Jorge Solla 1313 — https://zeno.fm/radio/jorge-solla-1313/`, timeout de conexão 12s e os eventos de exclusividade); o `RadioPlayer` (novo, client) porta classe-a-classe o gate `radio-player-proprio-ui-design.html` (Cenas 01–05: grid `112px/1fr/auto` com arte da estrela oficial sobre disco no desktop, linha compacta 76px no mobile, badge "Ao vivo", play amarelo 56/48px, share em linha inferior no compacto/mobile) com os estados `idle → connecting → playing | error` e fail-soft honesto: `play()` recusado, evento de erro ou conexão que aceita e não entrega áudio viram erro com "Tentar novamente" + "Ouvir no Zeno" + compartilhar — nunca spinner infinito (token de tentativa invalida promises superseded; retry re-dispara `load()`+`play()` porque o stream responde 302 com token por request). `RadioEmbed` é deletado (dono único, sem twin) e `JingleHomeSection` troca o filho; a **exclusividade rádio↔jingle** é broadcast `window` CustomEvent (`radio:play`/`jingle:play`, precedente `signature:created`): a rádio pausa o jingle no gesto do play e o `JingleCards` pausa só o próprio áudio ativo no `radio:play` — inerte em `/jingles`, que fica comportamentalmente intocado, assim como a grade, "Ver todos", o kill switch e a variante compact. Compartilhar reusa `Popover` + `useCopyFeedback` + `buildWhatsAppTextShareUrl` (mensagem literal no texto do WhatsApp e ao copiar; caixa exibe o literal). Home segue estática (leitura única `getPublishedJingleItems()`, tag `jingles`, MetaPixel intocado), sem PII/Consent/migration. Correção de design no fechamento: o `PopoverContent` portaled recebe `data-theme="campaign-site"` (mesmo padrão do `CardsStudio`) — sem o atributo os tokens da campanha somem no `document.body` e o CTA primário do WhatsApp renderizava invisível; crítica final do `designer` **CERTIFICADO** (tier `openai/gpt-5.6-sol`) contra o app renderizado em 390/1280 (cheia e compacta, estados idle/connecting/playing/erro/popover). Testes: unit novo/reescrito (`radio` 5, `radioPlayer` 11 — estados, timeout com fake timers, retry, `jingle:play`, superseded, `onError`, popover com tema, compact; `jinglePlayer` +3 casos de broadcast; `jingleHomeSection` sem iframe) e e2e adaptado (`frontend` + `frontendJingles`: iframe 0, `audio preload="none"`, 0 requests no load, stream só após o play, exclusividade nos dois sentidos, 48/56px, overflow 390) com o stub do fixture trocado do player zeno para `stream.zeno.fm` (MP3 local; request residual de `zeno.fm/player` agora falha como regressão). `pnpm gate:fast` verde (4226 unit), e2e local verde (36 passed), knip/cycles/format limpos; simplify com 2 revisores (9 achados: 7 resolvidos no lugar, 1 evento/tipo morto removido, 1 defer com gatilho no `*-impl.md` — unificar SVGs de play/pause na 3ª superfície), 0 Issues novas.
**S25 — Player próprio da Rádio 1313 na home (2026-09-21):** o iframe do zeno.fm que o S24 montou na home sai de cena e a Rádio Jorge Solla 1313 passa a tocar num **player próprio** — `<audio preload="none">` apontando direto para o stream (`stream.zeno.fm/hys86kx6k16tv`), com a cara da campanha e **zero request a terceiros antes do gesto de play** (o ganho colateral do S24 revertido para melhor: o pageview da home não fala mais com GA/rede de anúncio do widget). Sucessora explícita do S24 (#1235), que reverse de propósito os anti-goals dele ("não vira player custom") com nova evidência de produto registrada em `docs/plans/radio-player-proprio.md` (plano do item `done` não é reaberto). O contrato da rádio vive em `src/lib/radio.ts` (stream, página pública, título fixo "Rádio Jorge Solla 1313" = `icy-name`, mensagem literal `Ouça a Rádio Jorge Solla 1313 — https://zeno.fm/radio/jorge-solla-1313/`, timeout de conexão 12s e os eventos de exclusividade); o `RadioPlayer` (novo, client) porta classe-a-classe o gate `radio-player-proprio-ui-design.html` (Cenas 01–05: grid `112px/1fr/auto` com a **logo oficial da rádio** em corte circular no quadrado de arte no desktop — o NEEDS ASSET do gate fechado pelo C208 (`public/campaign-kit/radio/radio-jorge-solla-1313-logo.png`), rebase no head da main que trouxe o commit —, linha compacta 76px no mobile, badge "Ao vivo", play amarelo 56/48px, share em linha inferior no compacto/mobile) com os estados `idle → connecting → playing | error` e fail-soft honesto: `play()` recusado, evento de erro ou conexão que aceita e não entrega áudio viram erro com "Tentar novamente" + "Ouvir no Zeno" + compartilhar — nunca spinner infinito (token de tentativa invalida promises superseded; retry re-dispara `load()`+`play()` porque o stream responde 302 com token por request). `RadioEmbed` é deletado (dono único, sem twin) e `JingleHomeSection` troca o filho; a **exclusividade rádio↔jingle** é broadcast `window` CustomEvent (`radio:play`/`jingle:play`, precedente `signature:created`): a rádio pausa o jingle no gesto do play e o `JingleCards` pausa só o próprio áudio ativo no `radio:play` — inerte em `/jingles`, que fica comportamentalmente intocado, assim como a grade, "Ver todos", o kill switch e a variante compact. Compartilhar reusa `Popover` + `useCopyFeedback` + `buildWhatsAppTextShareUrl` (mensagem literal no texto do WhatsApp e ao copiar; caixa exibe o literal). Home segue estática (leitura única `getPublishedJingleItems()`, tag `jingles`, MetaPixel intocado), sem PII/Consent/migration. Correção de design no fechamento: o `PopoverContent` portaled recebe `data-theme="campaign-site"` (mesmo padrão do `CardsStudio`) — sem o atributo os tokens da campanha somem no `document.body` e o CTA primário do WhatsApp renderizava invisível; crítica final do `designer` **CERTIFICADO** (tier `openai/gpt-5.6-sol`) contra o app renderizado em 390/1280 (cheia e compacta, estados idle/connecting/playing/erro/popover). Testes: unit novo/reescrito (`radio` 5, `radioPlayer` 11 — estados, timeout com fake timers, retry, `jingle:play`, superseded, `onError`, popover com tema, compact; `jinglePlayer` +3 casos de broadcast; `jingleHomeSection` sem iframe) e e2e adaptado (`frontend` + `frontendJingles`: iframe 0, `audio preload="none"`, 0 requests no load, stream só após o play, exclusividade nos dois sentidos, 48/56px, overflow 390) com o stub do fixture trocado do player zeno para `stream.zeno.fm` (MP3 local; request residual de `zeno.fm/player` agora falha como regressão). `pnpm gate:fast` verde (4226 unit), e2e local verde (36 passed), knip/cycles/format limpos; simplify com 2 revisores (9 achados: 7 resolvidos no lugar, 1 evento/tipo morto removido, 1 defer com gatilho no `*-impl.md` — unificar SVGs de play/pause na 3ª superfície), 0 Issues novas.

View file

@ -92,7 +92,7 @@ A intenção declara "Vou apresentar dados? Não": sem agregação, sem PII, sem
**Opções:** A) `compact` = Cena 02 em qualquer largura (cartão centralizado em `max-w-[880px]`); `full` = Cena 01 no `md+` e o arranjo de linha da Cena 02 no mobile; B) `compact` = só geometria do S24 e layout por viewport; C) desenhar uma terceira composição para desktop compacto.
**Recomendação:** A — é a leitura que usa só as duas composições aprovadas: a Cena 02 é rotulada "0 jingles" (o estado compacto) e o mobile do `full` herda o arranjo mobile da Cena 02. Mapeamento bloco a bloco:
- **Cena 01 (full, md+):** `article` `overflow-hidden rounded-[14px] border border-(--campaign-line) bg-white shadow-[0_10px_28px_rgb(71_19_14/7%)] mt-9 p-3 md:p-4`; grid `md:grid md:grid-cols-[112px_1fr_auto] md:items-center md:gap-5`; `radio-art` `size-[76px] md:size-28` com disco = `<span aria-hidden>` `absolute inset-[13%] rounded-full border border-black/8 bg-white shadow-[0_7px_18px_rgb(71_19_14/10%)]` + `<Image src="/campaign-kit/estrela.png" ... className="relative z-[1] h-[64%] w-[64%] object-contain" />`; badge "Ao vivo" `text-[10px] md:rounded-full md:bg-(--pt-red)/8 md:px-3 md:py-1 md:text-[11px]` com live-dot `size-2 ... shadow-[0_0_0_4px_rgb(162_28_28/11%)]`; "Rádio online" `hidden text-xs text-(--campaign-muted) md:inline`; título `font-[family-name:var(--font-exo2)] text-lg leading-tight font-black tracking-[-0.025em] md:text-[28px] md:leading-none`; status `text-sm` (muted idle / `font-bold text-(--pt-red)` com dot em reprodução; spinner em connecting); ações `flex items-center gap-4 pr-2` com share `outline` + play `size-12 md:size-14` (`bg-(--pt-yellow)`, `text-(--pt-yellow-ink)`, `shadow-[0_6px_0_#cfb900,0_10px_22px_rgb(0_0_0/15%)]`, ícone 21/25px, `motion-reduce` como `JingleCards`).
- **Cena 01 (full, md+):** `article` `overflow-hidden rounded-[14px] border border-(--campaign-line) bg-white shadow-[0_10px_28px_rgb(71_19_14/7%)] mt-9 p-3 md:p-4`; grid `md:grid md:grid-cols-[112px_1fr_auto] md:items-center md:gap-5`; `radio-art` `size-[76px] md:size-28` com disco = `<span aria-hidden>` `absolute inset-[13%] rounded-full border border-black/8 bg-white shadow-[0_7px_18px_rgb(71_19_14/10%)]` + `<Image src="/campaign-kit/radio/radio-jorge-solla-1313-logo.png" ... className="relative z-[1] aspect-square w-[74%] rounded-full object-cover" />` (logo oficial da rádio, C208 — corte circular como o zeno exibe); badge "Ao vivo" `text-[10px] md:rounded-full md:bg-(--pt-red)/8 md:px-3 md:py-1 md:text-[11px]` com live-dot `size-2 ... shadow-[0_0_0_4px_rgb(162_28_28/11%)]`; "Rádio online" `hidden text-xs text-(--campaign-muted) md:inline`; título `font-[family-name:var(--font-exo2)] text-lg leading-tight font-black tracking-[-0.025em] md:text-[28px] md:leading-none`; status `text-sm` (muted idle / `font-bold text-(--pt-red)` com dot em reprodução; spinner em connecting); ações `flex items-center gap-4 pr-2` com share `outline` + play `size-12 md:size-14` (`bg-(--pt-yellow)`, `text-(--pt-yellow-ink)`, `shadow-[0_6px_0_#cfb900,0_10px_22px_rgb(0_0_0/15%)]`, ícone 21/25px, `motion-reduce` como `JingleCards`).
- **Cena 02 (compact):** `mx-auto mt-8 max-w-[880px] p-3`; linha `flex items-center gap-3` (art 76, título `text-lg`, play `size-12` com pause e `aria-pressed`); status `text-xs`; share `link` na linha inferior `mt-3 flex justify-end border-t border-(--campaign-line) pt-2`. No `full` mobile valem os mesmos valores mobile (o share outline inline só aparece em `md+`).
- **Cena 03 (connecting):** mesmo footprint; spinner `size-5 rounded-full border-2 border-(--pt-red)/18 border-t-(--pt-red) animate-spin [animation-duration:0.8s] motion-reduce:animate-none` + "Conectando à rádio…" (`role="status"`); play `opacity-60` `aria-busy="true"` `aria-label="Conectando"`.
- **Cena 04 (error):** branch próprio sem arte/badge — ícone `grid size-10 place-items-center rounded-full bg-(--pt-red)/10 text-(--pt-red)` (lucide `InfoIcon`), título "A rádio não conectou", copy "Tente de novo ou ouça na página da rádio.", ações `flex flex-wrap items-center gap-2`: "Tentar novamente" (`bg-(--pt-red) text-white min-h-11 rounded-lg`), "Ouvir no Zeno" (link sublinhado `RADIO_PAGE_URL`) e share `outline ml-auto`.
@ -132,7 +132,7 @@ A intenção declara "Vou apresentar dados? Não": sem agregação, sem PII, sem
- **Stub antigo removido como rede de regressão:** qualquer request residual a `zeno.fm/player/**` deixa de ser stubbado e derruba o guard de console/erro externo do fixture — prova ativa de que o iframe morreu.
- **`JingleCards` compartilhado com `/jingles`:** listener sempre ativo, mas pausa apenas o áudio ativo do próprio componente e não cria estado global; unit cobre "sem card ativo, `radio:play` não chama `pause`" e o e2e de `/jingles`/kill switch segue afirmando a página intocada.
- **Regressão de a11y/estado:** `data-state`, `aria-pressed`, `aria-busy`, `aria-label` e `role="status"` pinados em `radioPlayer.unit.spec.tsx`; `JINGLE_FOCUS_RING` em todos os controles (play, share, retry, link).
- **Arte assumida:** a estrela oficial (`public/campaign-kit/estrela.png`) é o candidato registrado (`NEEDS ASSET`); o ponto de troca é uma única `<Image src>`/`alt` no `RadioPlayer` — se o humano tiver capa própria, troca-se só a img, sem tocar em layout.
- **Arte resolvida (NEEDS ASSET fechado pelo C208):** a logo oficial da rádio entrou em `public/campaign-kit/radio/radio-jorge-solla-1313-logo.png` (C208, 2026-09-21); o player a exibe em corte circular (o contrato do zeno), no mesmo quadrado do gate — troca de uma única `<Image src>`/`alt`, sem tocar em layout. A estrela do kit segue sendo o ativo da campanha, não da rádio.
- **CSS/comentário órfão:** a varredura de fechamento (`RadioEmbed|zeno.fm/player|iframe` em `src/` = 0) mais o comentário de `styles.css:1158-1161` reescrito evitam documentação mentirosa; knip/ciclos cobrem o delete no gate.
- **Cache/ISR:** inalterado; os specs navegam `/?e2e=<ts>` como hoje e a home continua lendo uma única vez `getPublishedJingleItems()`.

View file

@ -305,11 +305,11 @@ export const RadioPlayer = ({ compact = false }: { compact?: boolean }) => {
className="absolute inset-[13%] rounded-full border border-[rgb(0_0_0/8%)] bg-white shadow-[0_7px_18px_rgb(71_19_14/10%)]"
/>
<Image
src="/campaign-kit/estrela.png"
alt="Estrela oficial da campanha Jorge Solla 1313"
width={72}
height={72}
className="relative z-[1] h-[64%] w-[64%] object-contain"
src="/campaign-kit/radio/radio-jorge-solla-1313-logo.png"
alt="Logo da Rádio Jorge Solla 1313"
width={112}
height={112}
className="relative z-[1] aspect-square w-[74%] rounded-full object-cover"
/>
</div>