mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 08:53:11 +00:00
docs(C193,C194,C195,C196,C197): planos + designs hi-fi — reels de tutoriais do site (skill + biblioteca privada)
Related #1152,#1153,#1154,#1155,#1156
This commit is contained in:
parent
5f8233c7e9
commit
cb01ae3ace
7 changed files with 2212 additions and 0 deletions
1009
docs/plans/reels-biblioteca-ui-design.html
Normal file
1009
docs/plans/reels-biblioteca-ui-design.html
Normal file
File diff suppressed because it is too large
Load diff
106
docs/plans/reels-biblioteca.md
Normal file
106
docs/plans/reels-biblioteca.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Biblioteca de reels na vertical Comunicação
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-18
|
||||
Issue: #1153
|
||||
Priority: P2
|
||||
Impeccable: C — fluxo novo em /campanha/comunicacao/reels (lista + detalhe), encaixe na vertical Comunicação
|
||||
Design UI: docs/plans/reels-biblioteca-ui-design.html (a produzir pelo `designer`) + assets
|
||||
Appetite: ~1–2 dias eng; um outcome verificável
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A produção de Reels do mandato (tutoriais das funcionalidades do site, começando pelos cards da home) acontece fora do site, mas a assessoria não tem onde se servir do material pronto. Hoje cada reel vive solto em pastas e conversas. Queremos uma biblioteca interna e privada dentro de `/campanha/comunicacao/reels`: a pessoa abre o reel, vê a capa e o título, assiste em 9:16 e baixa o que precisa — vídeo sem áudio (primário), vídeo com áudio rascunho, mp3 da narração, `.srt` e transcrição/roteiro. Nada é publicado no Instagram por aqui; a assessoria publica nos canais. Um kill switch permite despublicar (tirar da biblioteca) sem apagar.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação do mandato (role `communicator`), com coordinator e candidate enxergando o mesmo acervo; trabalha fora do site, em ferramentas de edição, e precisa da mídia bruta organizada.
|
||||
- **Job principal:** encontrar o reel certo, conferir o resultado, baixar os arquivos e regravar/acrescentar áudio quando necessário.
|
||||
- **Fluxo desejado:** entra na vertical Comunicação → Reels → varre a lista por capa/título → abre o detalhe → assiste em 9:16 → baixa o arquivo que precisa → (staff) despublica/republica quando o reel sai de linha.
|
||||
- **Anti-goals de produto:** não é editor de vídeo, não publica em rede social, não é galeria pública, não substitui a skill de produção (a mídia é gerada fora), não cria um novo modelo de mídia paralelo ao que o C193 definir.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[início: /campanha/comunicacao/reels]
|
||||
→ lista (capas + títulos, só reels publicados)
|
||||
→ detalhe /campanha/comunicacao/reels/[id]
|
||||
→ player 9:16 + painel de downloads (sem áudio · com áudio · mp3 · .srt · transcrição)
|
||||
→ baixa e trabalha a mídia fora do site
|
||||
→ (kill switch no detalhe: despublica → some da lista, arquivo preservado)
|
||||
[outcome: assessoria servida sem pedir arquivo a ninguém]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/reels-biblioteca-ui-design.html` — lista com capa/título e detalhe com player 9:16 + painel de downloads, na linguagem visual do /campanha.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- A assessoria abre `/campanha/comunicacao/reels`, encontra os reels publicados e assiste cada um em 9:16 sem sair do site.
|
||||
- No detalhe, todos os arquivos disponíveis do reel são baixáveis: vídeo sem áudio (primário), vídeo com áudio (quando existir), mp3 (quando existir), `.srt` e transcrição/roteiro em texto.
|
||||
- Despublicar tira o reel da lista imediatamente e não apaga nada; republicar o reativa.
|
||||
- Acesso restrito a `communicator`, `coordinator` e `candidate`; qualquer outro role é negado (fail-closed).
|
||||
- Guardrails: nada de publicação em redes sociais; nada de edição de vídeo na UI; nenhum reel some por acidente (kill switch reversível).
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — esta entrega é biblioteca de mídia, não análise. Sem métricas, sem gráficos.
|
||||
- **Decisões desbloqueadas:** qual reel está pronto para trabalhar; o que já tem narração/legenda pronta; o que foi despublicado (inventário operacional, não analítico).
|
||||
- **Forma:** adiada ao plano de implementação (o C193 define o modelo e a privacidade do arquivo).
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Rota da biblioteca: `/campanha/comunicacao/reels` (lista) + `/campanha/comunicacao/reels/[id]` (detalhe).
|
||||
- 3º sub-item de navegação da vertical Comunicação, depois de "Acervo de falas" e "Biblioteca de cortes".
|
||||
- Downloads oferecidos no detalhe: vídeo **sem áudio** (primário), vídeo **com áudio** (rascunho, quando existir), **mp3** da narração (quando existir), **`.srt`** e **transcrição/roteiro** em texto.
|
||||
- Kill switch: despublicar tira da biblioteca sem apagar; republicar reativa.
|
||||
- Acesso: só `communicator`, `coordinator`, `candidate`.
|
||||
- Nada de publicação em redes sociais por este item; nada de edição do vídeo na UI (a mídia é produzida fora, pela skill).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** rota nova em `src/app/(campaign)/campanha/(app)/comunicacao/reels/` (lista e detalhe, espelhando `acervo/cortes/`); componentes em `src/components/campaign/reels/` ou equivalentes; caminhos em `src/lib/campaignPaths.ts`; nav em `src/components/campaign/shell/nav.ts`; chrome em `src/lib/campaignPageChrome.ts`; gate em `src/utilities/campaignPageActor.ts`; RBAC reusando `src/lib/campaignRoles.ts` / `src/utilities/access/`.
|
||||
- **Precedente a olhar:** `acervo/cortes/` de ponta a ponta — `SpeechCutLibraryList/Card/Filters/StatusBadge/Player/PublicationPanel`, `speechCutPageData.ts`, `speechCutListUrl.ts`, handlers JSON `cortes/[id]/{publicacao,apagar,retry,texto}/route.ts`, teste e2e `campaignSpeechCut.e2e.spec.ts` e int `speechCut.int.spec.ts`. Reels é o mesmo formato (lista + detalhe + kill switch) com player 9:16 e painel de downloads.
|
||||
- **Risco de acoplamento:** (1) o cookie de sessão `campaign-token` (`path=/campanha`) não autentica `<video src="/api/media/file/...">` sozinho — o caminho autenticado de mídia depende do C193; (2) a regra de chrome de reels precisa vir ANTES do regex genérico de `/comunicacao/acervo/[^/]+`; (3) o C184 (#1106) mexe nos mesmos componentes de nav e `tests/unit/campaignNav.unit.spec.ts:100-127` pina a lista exata `[Acervo, Cortes]` — um 3º sub-item toca o mesmo diff.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C193 (crítico):** modelo do reel + privacidade do arquivo, incluindo o caminho autenticado de mídia.
|
||||
- **C184 (#1106, in-progress):** serializar com este lote para evitar conflito em nav/testes de sub-itens de Comunicação.
|
||||
- **Soft:** C195/C196/C197 — o acervo só tem conteúdo real quando o ingest e as skills existirem; a página é construída com fixtures antes disso.
|
||||
- **Design UI:** `docs/plans/reels-biblioteca-ui-design.html` a produzir pelo `designer` antes da implementação (gate).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Publicar, agendar ou cross-postar no Instagram ou qualquer rede.
|
||||
- Editar, cortar, legendar ou remixar vídeo dentro da UI.
|
||||
- Produção do reel (a skill gera a mídia fora do site).
|
||||
- Qualquer forma de acesso público ou compartilhamento externo do acervo.
|
||||
- Retenção, versionamento ou expurgo automático de arquivos despublicados (decisão adiada).
|
||||
- Empacotamento "baixar tudo" em zip.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Estúdio de edição.** Transformar o detalhe em editor de vídeo/legenda puxa escopo muito além da biblioteca. **Corte neste item:** só assistir e baixar.
|
||||
- **Versionar reels (v2, v3).** Controle de revisão não pedido. **Corte neste item:** cada reel é um registro único; trocar = substituir.
|
||||
- **Métricas do Instagram.** Outro produto, outra fonte de dados. **Corte neste item:** fora.
|
||||
- **Busca/filtros elaborados.** Com poucos reels, lista simples basta. **Corte neste item:** ordenação por mais recente no MVP.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **O kill switch pode ser acionado por `communicator` ou só `coordinator`?** **Opções:** A) qualquer um dos três roles com acesso à biblioteca; B) só coordinator/candidate. **Recomendação:** A — quem cuida do acervo no dia a dia é a assessoria; restringir cria fila.
|
||||
- **Reel despublicado continua acessível por link direto do detalhe para a assessoria?** **Opções:** A) sim, para quem tem login (é arquivo interno); B) não, some também no detalhe. **Recomendação:** A — biblioteca privada; despublicar é curadoria da lista, não revogação. **Conflita com a recomendação do C193 e precisa de decisão única no gate.**
|
||||
- **Lista precisa de busca/filtro no MVP?** **Opções:** A) só lista ordenada por mais recente; B) busca por título + filtro publicado/despublicado. **Recomendação:** A no MVP, B quando o acervo passar de uma dúzia.
|
||||
- **Quem pode ver/baixar a transcrição é o mesmo grupo dos downloads de mídia?** **Opções:** A) mesmo grupo, sem distinção; B) separar leitura de roteiro do download de vídeo. **Recomendação:** A — não há dado sensível distinto; evitar RBAC novo sem evidência.
|
||||
|
||||
## Referências
|
||||
|
||||
- Precedente no repo: `src/app/(campaign)/campanha/(app)/comunicacao/acervo/cortes/` (lista, detalhe, handlers JSON).
|
||||
- Componentes: `src/components/campaign/speech/`.
|
||||
- Regras: `.agents/rules/campanha-action-feedback.mdc`; `.agents/rules/engineering-standards.mdc` ("Edit the owner, don't twin").
|
||||
- Constraint de privacidade: `src/utilities/campaignAuth.ts` (`campaign-token`, `path=/campanha`) + C193.
|
||||
- Conflito de nav: C184 (#1106) e `tests/unit/campaignNav.unit.spec.ts:100-127`.
|
||||
- Design UI (gate): `docs/plans/reels-biblioteca-ui-design.html` + assets em `docs/plans/reels-biblioteca-ui-design-assets/`.
|
||||
- Contexto de produto: `docs/research/` (discovery aprovado) e o plano de arquitetura da campanha.
|
||||
90
docs/plans/reels-ingestao.md
Normal file
90
docs/plans/reels-ingestao.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Ingestão do pacote do reel na biblioteca privada do /campanha
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-18
|
||||
Issue: #1154
|
||||
Priority: P2
|
||||
Impeccable: A — N/A sem UI
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~1 dia eng; um outcome verificável
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A produção dos Reels do mandato termina na workstation, mas a entrega na biblioteca privada do `/campanha` ainda depende de trabalho manual: subir arquivo por arquivo, reencaixar campos e torcer para não duplicar acervo. Este item fecha essa última milha com um comando único que publica o pacote pronto — uma vez, no lugar certo e com conferência antes de gravar.
|
||||
|
||||
A forma é decisão travada com o humano: o comando roda no homeserver, onde os dados de produção vivem e onde os segredos já moram, e escreve pelo caminho oficial da aplicação. Nenhuma credencial de campanha circula na workstation, ninguém escreve por fora do Teqo, e o ensaio é sempre em staging antes de produção.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** quem produz os Reels do mandato (comunicação/assessoria) na workstation, já acostumado com o fluxo da skill de produção; roda o comando no homeserver, com pressa e querendo conferir antes de gravar.
|
||||
- **Job principal:** publicar um pacote de reel finalizado na biblioteca privada do `/campanha` com um comando, uma vez só, sem retrabalho.
|
||||
- **Fluxo desejado:** produz e empacota o reel → copia o pacote para o homeserver → roda o comando em modo conferência (vê o que será criado ou atualizado e para onde) → confirma a escrita → valida em staging → repete em produção → o reel aparece na biblioteca com vídeo, capa, narração, legenda e roteiro ligados à mesma entrada.
|
||||
- **Anti-goals de produto:** não é pipeline de mídia (não edita, não transcodifica, não gera capa); não publica em rede social; não é um segundo cadastro de conteúdo paralelo à biblioteca; não vira editor de metadados.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- O comando único ingere o pacote completo e o reel fica consultável na biblioteca privada com todos os artefatos associados à mesma entrada.
|
||||
- Re-ingerir o mesmo reel (mesma chave de identidade do pacote) atualiza a mesma entrada — nunca cria duplicata.
|
||||
- O padrão é conferência: sem confirmação explícita nada é escrito, e antes de qualquer escrita o comando mostra o alvo e o que será criado ou atualizado.
|
||||
- Alvo que não corresponde ao ambiente declarado é recusado sem escrita (fail-closed, como no resto do repo).
|
||||
- O caminho de validação passa sempre por staging antes de produção.
|
||||
- Pacote incompleto ou sem os campos obrigatórios (incluindo o texto alternativo da capa) é recusado com mensagem clara — sem meia-escrita.
|
||||
- Falha no meio do ingest não deixa entrada parcial visível na biblioteca.
|
||||
- Nenhuma credencial de campanha na workstation; nenhuma escrita fora do caminho oficial da aplicação.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — item de ingestão, sem tela nova.
|
||||
- **Decisões desbloqueadas:** comunicação (o reel está disponível na biblioteca para uso nos canais); coordenação (o acervo de reels reflete o que foi produzido).
|
||||
- **Forma:** _adiada ao plano de implementação_ — sem superfície de dados neste item.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Caminho de escrita: API do Payload (Local API) rodando no homeserver, na imagem de manutenção já existente — nunca SQL cru nem credencial de campanha na workstation.
|
||||
- Comando: `pnpm reels:ingest <diretório-do-pacote>`, rodado no homeserver, na imagem de manutenção do ambiente; o pacote chega por cópia (rsync) da skill.
|
||||
- Guard de intenção explícita: dry-run por padrão; escrita só com flag de confirmação (molde `MEDIA_RECOVER_CONFIRM`/`OPS79_MIGRATE_CONFIRM`); recusa de alvo que não case o ambiente declarado (`TEQO_ENV=staging|production`).
|
||||
- Idempotência: chave pelo hash do shot list gravado no `metadata.json` — re-ingerir o mesmo reel atualiza a mesma linha; hash novo = reel novo.
|
||||
- Staging primeiro, sempre: o caminho de validação é `teqo-staging` antes de `teqo-1313`.
|
||||
- Pacote do reel (nomes/artefatos): `reel.mp4` (sem áudio, primário), `reel-audio.mp4` (com áudio rascunho, quando houver), `narracao.mp3` (quando houver), `narracao.srt`, `roteiro.md`, `capa.png`, `metadata.json` (título, funcionalidade, duração, hash do shot list, data).
|
||||
- Sem publicação no Instagram; sem transcodificação no ingest.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `scripts/` (comando novo + helpers de `scripts/lib/cli.mjs`), imagem de manutenção (`Dockerfile`) e fluxo de deploy no homeserver; escrita via Local API como em `scripts/recover-media.mjs`/`scripts/seed-posts.mjs`; destino na biblioteca — `src/collections/Media.ts` + coleção do C193 (`src/payload.config.ts`).
|
||||
- **Precedente a olhar:** `scripts/recover-media.mjs` (dry-run/confirm/echo do alvo), `scripts/migrate-signature-orphan.mjs` (`--apply` + confirm), `scripts/bootstrap-staging-test-account.mjs` (guard por ambiente/banco), `scripts/seed-loader.mjs` (NODE_OPTIONS em script que sobe o Payload), `docs/ops/teqo-1313-deploy.md` (comando arbitrário na imagem de manutenção).
|
||||
- **Risco de acoplamento:** o modelo de destino é do C193; o ingest não pode furar o guard de ambiente nem desviar do adapter S3/proxy privado; os nomes do pacote são contrato com a skill de produção na workstation.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C193 — dura.** Sem a coleção do reel e a regra de privacidade definidas, não há onde ingerir nem como validar o aceite.
|
||||
- **Soft:** C196/C197 — o pacote real só existe quando as skills existirem; o ingest é testável com um pacote de fixture.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Transcodificação, normalização de áudio, geração de capa/poster e qualquer processamento de mídia (a imagem de manutenção não tem ffmpeg nem browser).
|
||||
- Publicação ou agendamento no Instagram e em outras redes (destino próprio, se um dia existir).
|
||||
- Edição do reel, regeração de roteiro/SRT ou correção de narração.
|
||||
- Editar metadados/artefatos de um reel já ingerido pelo comando — edição é no admin.
|
||||
- Arquivar/remover reels e gestão de acervo (C193).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Já que tem o comando, ele podia preparar a mídia".** Se alguém "só completar": ffmpeg na imagem, transcodificar, extrair poster, normalizar áudio → explosão da imagem de manutenção e do appetite. **Corte neste item:** o pacote chega pronto; o ingest valida campos/existência e grava.
|
||||
- **"Já que roda no servidor, deixa assistindo a pasta".** Watcher/daemon, ingest automático, retry infinito, publicação automática → vira plataforma de mídia. **Corte neste item:** comando manual e idempotente; automação só com pedido novo.
|
||||
- **"Já que lê metadados, deixa editar tudo no dry-run".** Vira editor de conteúdo e duplica o admin. **Corte neste item:** o ingest só cria/atualiza a partir do pacote; correção é no admin.
|
||||
- **"Faz direto na workstation com credencial".** A conveniência de rodar do jeito antigo fura a decisão travada. **Corte neste item:** só Local API no homeserver.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Pacote com artefatos opcionais ausentes passa?** **Opções:** A) sim, exigindo só vídeo primário + capa + metadados + legenda; B) exigir todos os artefatos. **Recomendação:** A — o reel mínimo útil precisa de vídeo, capa e legenda; áudio rascunho e narração ligam quando existirem. _(assumido — validar com produto)_
|
||||
- **O dry-run deve apontar se vai criar ou atualizar?** **Opções:** A) sim, por entrada/arquivo; B) só listar o que fará. **Recomendação:** A — a conferência é o ponto do guard; saber "atualiza esta entrada" evita susto em re-ingest.
|
||||
- **Mesmo hash de shot list com arquivos regerados (nova capa, novo corte)?** **Opções:** A) atualiza a entrada existente com os arquivos novos; B) recusa até hash novo. **Recomendação:** A — o hash identifica o reel; re-ingerir é atualizar, e é isso que evita duplicata.
|
||||
|
||||
## Referências
|
||||
|
||||
- `scripts/recover-media.mjs`, `scripts/lib/cli.mjs`, `scripts/migrate-signature-orphan.mjs`, `scripts/bootstrap-staging-test-account.mjs`, `scripts/seed-posts.mjs`, `scripts/seed-loader.mjs` — moldes de guard, dry-run e Local API em script
|
||||
- `Dockerfile`, `scripts/deploy-homeserver.sh`, `docs/ops/teqo-1313-deploy.md` — canal de execução no homeserver e envs por ambiente
|
||||
- `src/collections/Media.ts`, `src/payload.config.ts` — adapter S3 privado, proxy e `alt` obrigatório
|
||||
- `AGENTS.md` — fail-closed de ambiente e convenção de coleção própria
|
||||
- C193 — modelo de destino (coleção do reel + privacidade)
|
||||
89
docs/plans/reels-narracao.md
Normal file
89
docs/plans/reels-narracao.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Skill de reels: narração rascunho (TTS pt-BR) + transcrição sempre
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-18
|
||||
Issue: #1156
|
||||
Priority: P2
|
||||
Impeccable: A — N/A sem UI
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~1 dia eng; um outcome verificável — pacote com transcrição sempre e áudio rascunho opcional
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O ranking de valor dado pelo humano é explícito: o vídeo sozinho já ajuda; o texto da narração ajuda; o áudio, mais sucesso ainda. Este item completa o motor de reels do C196 com **voz e transcrição**: todo pacote sai com a transcrição do roteiro (`.srt` + `roteiro.md`) para a assessoria **regravar o áudio** ou acrescentar o áudio que quiser, e, quando o humano pedir, sai também um rascunho de narração em TTS pt-BR (`narracao.mp3`) e a variante do MP4 com esse áudio muxado. O vídeo continua sendo o formato primário; o áudio é rascunho, nunca voz clonada, e a decisão final do áudio é humana.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação do mandato, sem editor de vídeo; produz o reel pela skill e entrega/usa a mídia nos canais.
|
||||
- **Job principal:** ter o roteiro falado em mãos (para regravar) e, quando quiser, um áudio de rascunho já pronto na duração certa.
|
||||
- **Fluxo desejado:** roda a skill do C196 e chega ao gate de áudio → o gate pergunta **com ou sem áudio** (padrão: sem áudio) → sem áudio: o pacote sai com `reel.mp4` + `narracao.srt` + `roteiro.md`; com áudio: além disso, `narracao.mp3` + `reel-audio.mp4` (mesma trilha de vídeo, áudio muxado) → a assessoria publica o que quiser; se preferir a própria voz, regrava lendo o `roteiro.md`/`narracao.srt` e troca o áudio sem re-renderizar o vídeo.
|
||||
- **Anti-goals de produto:** não clona voz; não embute música licenciada; não publica em rede social; não é editor de áudio; não substitui a decisão humana sobre a voz final.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Transcrição **sempre** no pacote: `narracao.srt` com timecodes por beat e `roteiro.md` legível (fala + marcação de beat/cena).
|
||||
- Com áudio: `narracao.mp3` separado e `reel-audio.mp4` ao lado do `reel.mp4` sem áudio; a trilha de vídeo das duas variantes é idêntica.
|
||||
- Voz 100% TTS pt-BR institucional/rascunho; nenhum áudio do Solla é usado como referência ou clonagem.
|
||||
- O áudio rascunho cabe na duração do vídeo (a pessoa consegue trocar o áudio sem re-renderizar o vídeo).
|
||||
- O manifesto do pacote lista todos os arquivos gerados; a biblioteca (C194) baixa mp3, `.srt` e transcrição no detalhe do reel.
|
||||
- Nenhuma publicação automática em rede social; música licenciada não é embutida.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — áudio e texto do roteiro, sem superfície de dados.
|
||||
- **Decisões desbloqueadas:** a assessoria decide se aproveita o rascunho ou regrava com a voz que quiser; a coordenação decide o que vai ao ar.
|
||||
- **Forma:** _adiada ao plano de implementação_ — aqui só restrições de produto: transcrição sempre, áudio opcional, sem clonagem, sem música.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Transcrição **sempre** no pacote: `narracao.srt` (timecodes por beat) + `roteiro.md` legível (o que a assessoria usa para regravar).
|
||||
- Áudio quando houver: `narracao.mp3` + `reel-audio.mp4` (mesma trilha de vídeo com o áudio muxado), ao lado do `reel.mp4` sem áudio.
|
||||
- Voz: TTS pt-BR **institucional/rascunho**; **jamais clonar a voz do Solla**; o áudio final é decisão humana (a assessoria regrava ou aproveita o rascunho).
|
||||
- A skill pergunta no gate se o pacote sai **com ou sem áudio**; o padrão é **sem áudio + transcrição** (a maioria assiste no mudo; a legenda queimada é o formato primário).
|
||||
- Música: não embutir trilha licenciada (a assessoria usa o áudio do próprio Instagram); sem publicação automática em rede social.
|
||||
- O texto narrado é o roteiro aprovado no gate — a skill não parafraseia por conta própria.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** um módulo de narração dentro do mesmo diretório da skill C196 (o dono do motor de reels), atrás de um adapter fino de TTS (provider decidido na implementação); sem pipeline paralela.
|
||||
- **Precedente a olhar:** padrão ffmpeg do repo — array de args (nunca string de shell), `execFile` com timeout, `FFMPEG_PATH` override (`src/lib/speechCut.ts:157-195`, `src/utilities/speech/speechMediaPipeline.ts:18,73-97`); fixtures `tests/fixtures/fake-ffmpeg.mjs` e `tests/int/speechCut.int.spec.ts` como molde de teste; mux com `-c:v copy` (sem re-encode) e `+faststart`.
|
||||
- **Risco de acoplamento:** não existe TTS no repo hoje (o único áudio é STT, em `src/app/(campaign)/campanha/api/ai-transcribe/route.ts`); `edge-tts` é Python e a workstation tem Python 3.12 mas não `pip`/`pipx` — a instalação/fornecedor entra como decisão de implementação, fail-closed se o provider faltar.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C196 (duro):** motor de reels, estrutura do pacote/manifesto e o gate onde a pergunta "com ou sem áudio?" entra.
|
||||
- **C194 (soft):** o detalhe do reel já prevê os downloads de mp3, `.srt` e transcrição; este item produz os arquivos.
|
||||
- **ffmpeg real** no ambiente (pré-requisito herdado do C196).
|
||||
- **Decisão de fornecedor/instalação do TTS** (ver questões em aberto).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Clonagem de voz (deepfake) ou uso de gravações do Solla no TTS.
|
||||
- Embutir música/trilha licenciada.
|
||||
- Publicação automática no Instagram ou em qualquer rede (inclusive marcar `is_ai_generated` — a declaração, se necessária, é humana).
|
||||
- Edição de áudio (cortes, mixagem, loudness avançado) e interface de gravação.
|
||||
- STT/legenda automática a partir de fala — o reel é roteirizado; transcrição vem do shot list.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Polir a naturalidade do TTS além do "rascunho publicável".** O áudio é draft; a assessoria regrava. **Corte neste item:** inteligibilidade na duração certa.
|
||||
- **Normalização de loudness e drift de duração.** **Corte neste item:** se o par `reel.mp4`/`reel-audio.mp4` sai consistente, refino depois.
|
||||
- **Quebra de linha/timestamps perfeitos no `.srt`.** **Corte neste item:** um bloco por beat, simples.
|
||||
- **Múltiplas takes/variações de narração.** **Corte neste item:** uma por render.
|
||||
- **Waveform/preview de áudio na biblioteca.** **Corte neste item:** player/download; preview é outro item.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Provider TTS?** **Opções:** A) `edge-tts` (grátis, sem key, vozes neurais Microsoft) | B) Google Cloud TTS (1M chars/mês grátis, ~US$16/1M depois) | C) Azure (500k/mês grátis) | D) ElevenLabs (melhor pt-BR, caro). **Recomendação:** A como rascunho (custo zero e sem credencial), com B/C como upgrade se a qualidade incomodar. OpenAI está reservado ao design no Teqo. _(assumido — validar com produto)_
|
||||
- **Como instalar o TTS na workstation sem `pip`/`pipx`?** **Opções:** A) venv Python dedicado | B) provider HTTP com key no env | C) binário empacotado. **Recomendação:** A para `edge-tts` (sem key, isolado no venv); B se o provider escolhido for cloud. _(assumido)_
|
||||
- **O gate deve permitir edição humana da narração antes do TTS?** **Opções:** A) não — narra o roteiro aprovado | B) sim, editar o texto falado. **Recomendação:** A no primeiro ciclo; a edição é no shot list. _(assumido)_
|
||||
- **Formato do mp3 e do mux?** **Opções:** A) mono ~96–128 kbps, AAC no mp4 com `+faststart`. **Recomendação:** A — suficiente para rascunho e leve para download.
|
||||
|
||||
## Referências
|
||||
|
||||
- C196 (motor de reels), C194 (biblioteca) e C195 (ingestão) — planos irmãos.
|
||||
- `src/lib/speechCut.ts:157-195` e `src/utilities/speech/speechMediaPipeline.ts:18,73-97` — padrão ffmpeg.
|
||||
- `tests/fixtures/fake-ffmpeg.mjs`, `tests/int/speechCut.int.spec.ts` — molde de teste.
|
||||
- `src/app/(campaign)/campanha/api/ai-transcribe/route.ts` — precedente de áudio (STT).
|
||||
- Pesquisa web 2026-09-18: TTS pt-BR (`edge-tts`, Google, Azure, ElevenLabs) e `is_ai_generated` do Instagram.
|
||||
88
docs/plans/reels-reel-privado.md
Normal file
88
docs/plans/reels-reel-privado.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Reel privado no CMS
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-18
|
||||
Issue: #1152
|
||||
Priority: P2
|
||||
Impeccable: A — N/A sem UI
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~1 dia eng; um reel registrável de ponta a ponta com arquivos privados e kill switch
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A assessoria já produz Reels de tutorial das funcionalidades do site (começando pelos cards de `#cards`) fora do Teqo, mas o resultado se perde em pastas e conversas. Quero que cada reel exista no sistema: título, funcionalidade-alvo, os artefatos de mídia e um estado que funcione como kill switch. Os arquivos são da campanha, **privados e não listados** — só abrem com login; o site público não muda em nada. Nada de publicação automática no Instagram: a assessoria publica pelos canais dela. Este item entrega o registro no CMS, não o fluxo editorial completo de produção.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** pessoa da assessoria de comunicação que produz o reel fora do Teqo; coordinator/candidate revisam e podem desligar um reel.
|
||||
- **Job principal:** registrar o reel com seus artefatos num lugar privado e confiável, com um estado que tire do ar num clique.
|
||||
- **Fluxo desejado:** produz fora → sobe o registro com título, funcionalidade-alvo (`cards`) e artefatos (vídeo sem áudio como primário; variante com áudio quando houver; mp3 da narração rascunho; transcrição; `.srt`; capa) → estado rascunho → aprovação → publicado na biblioteca privada → se preciso, despublicado tira do ar.
|
||||
- **Anti-goals de produto:** não é editor de vídeo; não é galeria pública; não publica sozinho em rede social; não cria segundo cadastro de pessoa.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Um reel é registrável com título, funcionalidade-alvo e estado `draft → published → unpublished`.
|
||||
- Todos os artefatos previstos podem ser anexados ao mesmo registro, com o vídeo sem áudio como primário.
|
||||
- Nenhum arquivo de reel abre sem login da campanha (anônimo e navegador sem sessão recebem não).
|
||||
- Aprovar publica direto na biblioteca privada e **não** dispara nenhuma ação externa.
|
||||
- `unpublished` funciona de fato: o reel some da biblioteca da assessoria e o arquivo deixa de ser servido.
|
||||
- Só os papéis da comunicação veem e gerenciam; advisors e leaders ficam fora.
|
||||
- O site público e os cards permanecem idênticos.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não
|
||||
- **Decisões desbloqueadas:** saber quais funcionalidades já têm tutorial e onde estão os arquivos; base futura para cobertura de funcionalidades.
|
||||
- **Forma:** adiada ao plano de implementação
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Status do reel: `draft | published | unpublished` (kill switch = `unpublished`; aprovar publica direto na biblioteca privada → `published`).
|
||||
- Funcionalidade-alvo (feature): começa por `cards` — a funcionalidade dos cards de apoio em `#cards`; o vocabulário cresce conforme novos tutoriais.
|
||||
- Papéis com acesso: os mesmos da vertical de comunicação — `communicator`, `coordinator`, `candidate`; advisors e leaders fora.
|
||||
- Artefatos: vídeo `mp4` 1080×1920 (sem áudio = primário; com áudio = variante), áudio `mp3` (rascunho da narração), transcrição/roteiro em texto, legendas `.srt`, capa `png/jpg` 1080×1920.
|
||||
- Guardrails: **nunca** publicar automaticamente no Instagram; o site público não muda; arquivo só abre com login da campanha; sem clone de voz; sem segundo cadastro de pessoa.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** coleção nova na vertical de Comunicação (precedente `src/collections/SpeechCut.ts`), artefatos privados na esteira do `Media` (upload + storage privado por proxy), auth da campanha e superfície do admin Payload — sem tela pública.
|
||||
- **Precedente a olhar:** `SpeechCut` (grupo `'Comunicação'`, `status` com descrição de kill switch, relationship `media`); acessos em `src/utilities/access/speeches.ts` re-exportados por `src/utilities/campaignAccess.ts`; roles em `src/lib/campaignRoles.ts`.
|
||||
- **Ponto sensível (decidir na implementação):** o cookie de campanha tem `path: '/campanha'` e `<video>`/`<img>` não mandam header — privacidade de arquivo provavelmente exige rota autenticada sob `/campanha` servindo o objeto (proxy) ou URL assinada. Não fechar aqui.
|
||||
- **Vocabulário de feature:** não existe catálogo; os `data-home-section` da home e os ids de `src/lib/cardModels.ts` são a matéria-prima mais próxima.
|
||||
- **Risco de acoplamento:** `Media` hoje é leitura pública (pino em `tests/int/collectionAccessLockdown.int.spec.ts:224-249`); o caminho privado novo não pode afrouxar nem quebrar esse contrato.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Soft:** C196/C197 — são os produtores dos artefatos que alimentam este modelo; o modelo não depende delas para existir.
|
||||
- **Soft:** C195 — o ingest é o caminho preferido de escrita, mas o admin continua sendo uma porta válida.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Publicação automática ou agendada no Instagram.
|
||||
- Qualquer mudança no site público, na home ou nos cards.
|
||||
- Edição de vídeo, legenda ou áudio dentro do sistema.
|
||||
- Métricas/analytics do que foi publicado no Instagram.
|
||||
- CRUD de um catálogo de funcionalidades (o vocabulário começa fixo em `cards`).
|
||||
- Dashboard de biblioteca além do admin (é o C194).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Pipeline de mídia.** Se alguém "só completar" com transcode, derivação de thumb ou compressão, vira projeto de encoding. **Corte neste item:** aceitar os arquivos como enviados, sem processamento.
|
||||
- **Variantes e versionamento.** "Só mais uma versão" do vídeo/roteiro vira controle de revisão. **Corte neste item:** um artefato por formato; trocar = substituir.
|
||||
- **Legenda/voz automática.** "Só gerar o `.srt` sozinho" puxa STT e beira clone de voz. **Corte neste item:** legendas e transcrição vêm prontas de fora.
|
||||
- **Biblioteca pública.** "Só uma galeria no site" atravessa a decisão travada de privacidade. **Corte neste item:** nada público.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Vocabulário de feature: lista fechada ou texto livre?** **Opções:** A) lista de ids conhecidos (`cards`, …) | B) texto livre com sugestões. **Recomendação:** A, começando por `cards`, crescendo por lista — dá agrupamento e evita variação de grafia. _(assumido — validar com produto)_
|
||||
- **Quem aprova o reel (`draft → published`)?** **Opções:** A) qualquer um dos três papéis | B) só coordinator/candidate. **Recomendação:** A — a assessoria produz e aprova; coordinator/candidate mantêm o kill switch. _(assumido — validar com produto)_
|
||||
- **`unpublished` esconde só da listagem ou também invalida o arquivo?** **Opções:** A) some da biblioteca, arquivo ainda abre com login | B) corta também o acesso ao arquivo. **Recomendação:** B — kill switch precisa ser de verdade; **conflita com a recomendação do C194 e precisa de decisão única no gate.**
|
||||
|
||||
## Referências
|
||||
|
||||
- `src/collections/SpeechCut.ts`, `src/utilities/access/speeches.ts`, `src/utilities/campaignAccess.ts`
|
||||
- `src/collections/Media.ts`, `src/utilities/mediaStorage.ts`, `src/payload.config.ts`
|
||||
- `src/utilities/campaignAuth.ts`, `src/lib/campaignRoles.ts`
|
||||
- Home `#cards` e `src/lib/cardModels.ts` (matéria-prima do vocabulário de feature)
|
||||
- `tests/int/collectionAccessLockdown.int.spec.ts` (contrato de leitura anônima de `media`)
|
||||
714
docs/plans/reels-tutoriais-ui-design.html
Normal file
714
docs/plans/reels-tutoriais-ui-design.html
Normal file
|
|
@ -0,0 +1,714 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign-site">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — C196 · Sistema visual dos reels tutoriais</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--pt-red);
|
||||
--color-primary-foreground: #ffffff;
|
||||
--color-muted: #ebe9e9;
|
||||
--color-muted-foreground: var(--campaign-muted);
|
||||
--color-border: var(--campaign-line);
|
||||
}
|
||||
[data-theme='campaign-site'] {
|
||||
--background: #fff8f2;
|
||||
--foreground: #000000;
|
||||
--pt-red: #a21c1c;
|
||||
--pt-red-dark: #47130e;
|
||||
--pt-yellow: #ffe607;
|
||||
--campaign-cream: #fff8f2;
|
||||
--campaign-muted: #6c615e;
|
||||
--campaign-surface: #f9faff;
|
||||
--campaign-band: #ebe9e9;
|
||||
--campaign-line: rgb(0 0 0 / 12%);
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.brexter {
|
||||
/* Brexter is the approved face; Impact is the standalone gate fallback. */
|
||||
font-family: Brexter, Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
|
||||
font-weight: 900;
|
||||
}
|
||||
.artifact-grid {
|
||||
background-color: #ebe9e9;
|
||||
background-image: radial-gradient(rgb(71 19 14 / 10%) 0.7px, transparent 0.7px);
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
.frame {
|
||||
position: relative;
|
||||
aspect-ratio: 9 / 16;
|
||||
overflow: hidden;
|
||||
background: #fff8f2;
|
||||
container-type: inline-size;
|
||||
}
|
||||
.crop-preview {
|
||||
container-type: inline-size;
|
||||
}
|
||||
.frame-shadow {
|
||||
box-shadow: 0 22px 62px rgb(71 19 14 / 18%);
|
||||
}
|
||||
.site-placeholder {
|
||||
background:
|
||||
linear-gradient(180deg, rgb(255 255 255 / 6%), transparent 24%),
|
||||
linear-gradient(145deg, #dedbd8, #aaa39e);
|
||||
}
|
||||
.safe-line {
|
||||
border-color: rgb(255 230 7 / 82%);
|
||||
}
|
||||
.crop-line {
|
||||
border-color: rgb(255 255 255 / 78%);
|
||||
}
|
||||
.logo-lockup {
|
||||
letter-spacing: -0.055em;
|
||||
transform: skew(-4deg);
|
||||
}
|
||||
.cursor {
|
||||
filter: drop-shadow(0 2px 2px rgb(0 0 0 / 35%));
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="artifact-grid min-h-screen text-foreground antialiased">
|
||||
<main class="mx-auto max-w-[1500px] px-4 py-10 md:px-8 md:py-14">
|
||||
<header class="max-w-4xl">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="rounded-full bg-(--pt-red) px-2.5 py-1 text-xs font-semibold text-white"
|
||||
>C196 · gate hi-fi</span
|
||||
>
|
||||
<span
|
||||
class="rounded-full border border-black/10 bg-(--campaign-cream) px-2.5 py-1 text-xs font-medium"
|
||||
>Vídeo 1080 × 1920 · 9:16</span
|
||||
>
|
||||
<span
|
||||
class="rounded-full border border-black/10 bg-(--campaign-cream) px-2.5 py-1 text-xs font-medium"
|
||||
>Sem áudio · legenda queimada</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight">Tutorial que se entende no mudo</h1>
|
||||
<p class="mt-2 max-w-3xl text-sm leading-6 text-muted-foreground">
|
||||
Um sistema de frames para ensinar o site com ritmo, contraste e marca — sem competir com a
|
||||
gravação. O vermelho enquadra a história; amarelo aponta a ação; Brexter carrega o
|
||||
impacto.
|
||||
</p>
|
||||
<div class="mt-5 flex flex-wrap gap-2 text-xs">
|
||||
<a
|
||||
href="#sequencia"
|
||||
class="rounded-full border border-black/10 bg-white px-3 py-1.5 font-medium"
|
||||
>01–03 · Sequência</a
|
||||
>
|
||||
<a
|
||||
href="#safe-zone"
|
||||
class="rounded-full border border-black/10 bg-white px-3 py-1.5 font-medium"
|
||||
>04 · Safe zone</a
|
||||
>
|
||||
<a
|
||||
href="#capa"
|
||||
class="rounded-full border border-black/10 bg-white px-3 py-1.5 font-medium"
|
||||
>05 · Capa e recortes</a
|
||||
>
|
||||
<a
|
||||
href="#spec"
|
||||
class="rounded-full border border-black/10 bg-white px-3 py-1.5 font-medium"
|
||||
>06 · Sistema</a
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- SEQUÊNCIA -->
|
||||
<section id="sequencia" class="mt-16" aria-labelledby="sequencia-title">
|
||||
<div class="mb-5">
|
||||
<p class="text-xs font-semibold text-(--pt-red)">CENAS 01–03 · LINHA DO TEMPO</p>
|
||||
<h2 id="sequencia-title" class="mt-1 text-xl font-semibold tracking-tight">
|
||||
Hook → gesto → ação
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Frames em escala. Tudo crítico permanece no miolo seguro.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-8 md:grid-cols-3">
|
||||
<!-- HOOK -->
|
||||
<article>
|
||||
<div class="mb-3 flex items-end justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">01 · Hook</p>
|
||||
<p class="text-xs text-muted-foreground">0–1,5s</p>
|
||||
</div>
|
||||
<span class="rounded-full bg-black px-2 py-1 text-[10px] font-semibold text-white"
|
||||
>ABERTURA</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="frame frame-shadow mx-auto w-full max-w-[360px] rounded-[22px] border border-black/15 bg-(--pt-red)"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-0 bg-[radial-gradient(circle_at_85%_18%,rgb(255_230_7/28%),transparent_22%),linear-gradient(160deg,#a21c1c_0%,#8f1717_55%,#47130e_100%)]"
|
||||
></div>
|
||||
<div
|
||||
class="absolute -right-[12%] top-[15%] size-[52%] rotate-12 rounded-[30%] border-[14px] border-(--pt-yellow)/90"
|
||||
></div>
|
||||
<div class="absolute left-[6%] right-[6%] top-[14%]">
|
||||
<div
|
||||
class="inline-flex rounded-full bg-(--pt-yellow) px-[4%] py-[1.7%] text-[3.5cqw] font-black text-black"
|
||||
>
|
||||
FAÇA PARTE
|
||||
</div>
|
||||
<h3
|
||||
class="brexter mt-[8%] max-w-[88%] text-[12cqw] leading-[0.88] uppercase tracking-[-0.045em] text-white"
|
||||
>
|
||||
Seu apoio<br />vira card<br /><span class="text-(--pt-yellow)">em segundos.</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="absolute inset-x-[6%] bottom-[15%] flex items-end justify-between gap-3">
|
||||
<p class="max-w-[58%] text-[3.8cqw] font-semibold leading-[1.2] text-white">
|
||||
Veja como criar o seu pelo celular.
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<p class="logo-lockup brexter text-[8cqw] leading-none text-white">SOLLA</p>
|
||||
<p class="text-[2.6cqw] font-bold text-(--pt-yellow)">13 13</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="absolute bottom-[5%] left-[6%] rounded bg-black/45 px-2 py-1 text-[2.5cqw] text-white"
|
||||
>NEEDS ASSET · logo oficial</span
|
||||
>
|
||||
</div>
|
||||
<p class="mx-auto mt-3 max-w-[360px] text-xs leading-5 text-muted-foreground">
|
||||
<strong class="text-foreground">Movimento:</strong> título entra em dois beats; aro
|
||||
amarelo expande 96% → 100%. Sem zoom de site ainda.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<!-- PASSO -->
|
||||
<article>
|
||||
<div class="mb-3 flex items-end justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">02 · Passo</p>
|
||||
<p class="text-xs text-muted-foreground">demonstração</p>
|
||||
</div>
|
||||
<span
|
||||
class="rounded-full bg-(--pt-yellow) px-2 py-1 text-[10px] font-black text-black"
|
||||
>1 / 3</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="frame frame-shadow mx-auto w-full max-w-[360px] rounded-[22px] border border-black/15 bg-black"
|
||||
>
|
||||
<div class="site-placeholder absolute inset-0">
|
||||
<div class="absolute inset-x-0 top-0 h-[7%] bg-(--pt-red)">
|
||||
<div
|
||||
class="absolute left-[6%] top-1/2 h-[18%] w-[28%] -translate-y-1/2 rounded-full bg-white/75"
|
||||
></div>
|
||||
<div
|
||||
class="absolute right-[6%] top-1/2 size-[7%] -translate-y-1/2 rounded-full bg-white/25"
|
||||
></div>
|
||||
</div>
|
||||
<div class="absolute inset-x-[6%] top-[11%] text-center">
|
||||
<p class="text-[2.4cqw] font-black tracking-[0.1em] text-(--pt-red) uppercase">
|
||||
Faça parte
|
||||
</p>
|
||||
<h4 class="mt-[1%] text-[5cqw] font-black leading-tight">
|
||||
Mostre que você está com Solla
|
||||
</h4>
|
||||
<p
|
||||
class="mx-auto mt-[2%] max-w-[86%] text-[2.7cqw] leading-[1.3] text-(--campaign-muted)"
|
||||
>
|
||||
Escolha um modelo, coloque seu nome ou sua foto e baixe seu card.
|
||||
</p>
|
||||
</div>
|
||||
<div class="absolute inset-x-[6%] top-[28%] flex gap-[3%]">
|
||||
<div
|
||||
class="aspect-square flex-1 rounded-[6%] border border-black/10 bg-(--campaign-cream) p-[3%]"
|
||||
>
|
||||
<div class="h-full rounded-[5%] bg-(--pt-red)"></div>
|
||||
<p class="mt-[3%] text-center text-[2.1cqw] font-bold">Card com seu nome</p>
|
||||
</div>
|
||||
<div
|
||||
class="aspect-square flex-1 rounded-[6%] border border-black/10 bg-white p-[3%] opacity-70"
|
||||
>
|
||||
<div class="h-full rounded-[5%] bg-stone-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-x-[5%] bottom-[22%] rounded-[5%] bg-white p-[5%] shadow-xl ring-1 ring-black/10"
|
||||
>
|
||||
<p class="text-[4.5cqw] font-black">Personalize com seu nome</p>
|
||||
<div
|
||||
class="mt-[4%] h-[10cqw] rounded-lg border border-black/20 px-[4%] py-[3%] text-[3.3cqw] text-black/60"
|
||||
>
|
||||
Seu nome
|
||||
</div>
|
||||
<div
|
||||
class="mt-[4%] h-[10cqw] rounded-lg bg-(--pt-red) pt-[3%] text-center text-[3.2cqw] font-bold text-white"
|
||||
>
|
||||
Criar meu card
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- badge de passo -->
|
||||
<div
|
||||
class="absolute left-[6%] top-[15%] flex items-center gap-[2%] rounded-full bg-(--pt-yellow) px-[4%] py-[2%] text-[3.2cqw] font-black text-black shadow-lg"
|
||||
>
|
||||
<span
|
||||
class="grid size-[6cqw] place-items-center rounded-full bg-black text-[2.8cqw] text-white"
|
||||
>1</span
|
||||
>Escolha um modelo
|
||||
</div>
|
||||
<!-- zoom target -->
|
||||
<div
|
||||
class="absolute left-[5%] top-[27%] aspect-square w-[47%] rounded-[7%] border-[3px] border-(--pt-yellow) shadow-[0_0_0_5px_rgb(255_230_7/18%)]"
|
||||
></div>
|
||||
<svg
|
||||
class="cursor absolute left-[40%] top-[49%] w-[8%] text-white"
|
||||
viewBox="0 0 24 30"
|
||||
fill="currentColor"
|
||||
stroke="#000"
|
||||
stroke-width="1.4"
|
||||
>
|
||||
<path d="M3 2v22l6-6 4 9 4-2-4-9h8L3 2Z" />
|
||||
</svg>
|
||||
<div class="absolute inset-x-[6%] top-[60%] flex justify-center">
|
||||
<p
|
||||
class="max-w-[90%] rounded-[4%] bg-black/86 px-[5%] py-[3.5%] text-center text-[4.1cqw] font-bold leading-[1.18] text-white shadow-xl"
|
||||
>
|
||||
Toque em <span class="text-(--pt-yellow)">“Card com seu nome”</span>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="absolute bottom-[13%] right-[6%] text-right">
|
||||
<p class="logo-lockup brexter text-[6cqw] leading-none text-white drop-shadow">
|
||||
SOLLA
|
||||
</p>
|
||||
<p class="text-[2.1cqw] font-black text-(--pt-yellow)">13 13</p>
|
||||
</div>
|
||||
<span
|
||||
class="absolute bottom-[5%] left-[6%] rounded bg-black/55 px-2 py-1 text-[2.5cqw] text-white"
|
||||
>NEEDS ASSET · captura real + logo</span
|
||||
>
|
||||
</div>
|
||||
<p class="mx-auto mt-3 max-w-[360px] text-xs leading-5 text-muted-foreground">
|
||||
<strong class="text-foreground">Câmera:</strong> zoom suave até 118% no tile; cursor
|
||||
chega 180ms antes do clique; halo amarelo pulsa uma vez. A legenda fica a ~1160px no
|
||||
arquivo final.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<!-- CTA -->
|
||||
<article>
|
||||
<div class="mb-3 flex items-end justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">03 · Fechamento / CTA</p>
|
||||
<p class="text-xs text-muted-foreground">últimos 2–3s</p>
|
||||
</div>
|
||||
<span class="rounded-full bg-black px-2 py-1 text-[10px] font-semibold text-white"
|
||||
>FINAL</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="frame frame-shadow mx-auto w-full max-w-[360px] rounded-[22px] border border-black/15 bg-(--campaign-cream)"
|
||||
>
|
||||
<div class="absolute inset-x-0 top-0 h-[47%] bg-(--pt-red)">
|
||||
<div
|
||||
class="absolute -left-[10%] top-[22%] size-[55%] -rotate-12 rounded-[32%] border-[14px] border-(--pt-yellow)/90"
|
||||
></div>
|
||||
</div>
|
||||
<div class="absolute inset-x-[7%] top-[14%] text-center text-white">
|
||||
<p class="text-[3cqw] font-black tracking-[0.12em] uppercase text-(--pt-yellow)">
|
||||
Seu card, sua voz
|
||||
</p>
|
||||
<h3 class="brexter mt-[4%] text-[12cqw] leading-[0.9] uppercase">
|
||||
Agora é<br />com você.
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-x-[7%] top-[51%] rounded-[6%] bg-white p-[7%] text-center shadow-[0_14px_40px_rgb(71_19_14/14%)] ring-1 ring-black/10"
|
||||
>
|
||||
<div
|
||||
class="mx-auto grid size-[14cqw] place-items-center rounded-full bg-(--pt-yellow)"
|
||||
>
|
||||
<svg
|
||||
class="size-[7cqw]"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.3"
|
||||
>
|
||||
<path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7" />
|
||||
<path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-[5%] text-[4cqw] font-semibold">Acesse o site pelo</p>
|
||||
<p class="brexter mt-[1%] text-[8cqw] leading-none uppercase text-(--pt-red)">
|
||||
link na bio
|
||||
</p>
|
||||
<p class="mt-[4%] text-[3cqw] leading-[1.35] text-(--campaign-muted)">
|
||||
Escolha seu modelo, crie e compartilhe.
|
||||
</p>
|
||||
</div>
|
||||
<div class="absolute inset-x-[7%] bottom-[11%] flex items-end justify-between">
|
||||
<p
|
||||
class="max-w-[52%] text-[2.8cqw] font-semibold leading-[1.3] text-(--pt-red-dark)"
|
||||
>
|
||||
jorgesolla1313.com.br
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<p class="logo-lockup brexter text-[7cqw] leading-none text-(--pt-red)">SOLLA</p>
|
||||
<p class="text-[2.5cqw] font-black">13 13</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="absolute bottom-[4%] left-[7%] rounded bg-black/75 px-2 py-1 text-[2.5cqw] text-white"
|
||||
>NEEDS ASSET · logo oficial</span
|
||||
>
|
||||
</div>
|
||||
<p class="mx-auto mt-3 max-w-[360px] text-xs leading-5 text-muted-foreground">
|
||||
<strong class="text-foreground">Final:</strong> sem botão falso. “Link na bio” é a
|
||||
única instrução; URL reforça a origem. Segura por 2s para leitura.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SAFE ZONE -->
|
||||
<section id="safe-zone" class="mt-20" aria-labelledby="safe-zone-title">
|
||||
<div class="mb-5">
|
||||
<p class="text-xs font-semibold text-(--pt-red)">CENA 04 · SAFE ZONE</p>
|
||||
<h2 id="safe-zone-title" class="mt-1 text-xl font-semibold tracking-tight">
|
||||
O Instagram ocupa as bordas; o tutorial ocupa o miolo
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-sm text-muted-foreground">
|
||||
Guias são apenas de produção e não aparecem no render final.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-8 lg:grid-cols-[400px_minmax(0,1fr)]">
|
||||
<div
|
||||
class="frame frame-shadow mx-auto w-full max-w-[400px] rounded-[22px] border border-black/15 bg-black"
|
||||
>
|
||||
<div class="site-placeholder absolute inset-0 opacity-85">
|
||||
<div class="absolute inset-x-0 top-0 h-[7%] bg-(--pt-red)"></div>
|
||||
<div class="absolute inset-x-[8%] top-[23%] h-[31%] rounded-[4%] bg-white/70"></div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-x-[6%] top-[14%] bottom-[35%] border-2 border-dashed safe-line"
|
||||
></div>
|
||||
<div class="absolute inset-x-0 top-0 h-[14%] bg-(--pt-red-dark)/72 p-[5%] text-white">
|
||||
<p class="text-[3.4cqw] font-bold">~250px · topo</p>
|
||||
<p class="mt-[1%] text-[2.5cqw] text-white/80">nome, áudio e chrome podem cobrir</p>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-x-0 bottom-0 h-[35%] bg-(--pt-red-dark)/78 p-[5%] text-white"
|
||||
>
|
||||
<p class="text-[3.4cqw] font-bold">~672px · base conservadora</p>
|
||||
<p class="mt-[1%] max-w-[64%] text-[2.5cqw] leading-tight text-white/80">
|
||||
legenda, descrição e botões do Instagram disputam esta área
|
||||
</p>
|
||||
</div>
|
||||
<div class="absolute inset-y-[14%] left-0 w-[6%] bg-(--pt-yellow)/45"></div>
|
||||
<div class="absolute inset-y-[14%] right-0 w-[6%] bg-(--pt-yellow)/45"></div>
|
||||
<div class="absolute inset-x-[6%] top-[47%] flex justify-center">
|
||||
<p
|
||||
class="max-w-[88%] rounded-[4%] bg-black/88 px-[5%] py-[3.5%] text-center text-[4cqw] font-bold leading-[1.18] text-white"
|
||||
>
|
||||
Legenda entre <span class="text-(--pt-yellow)">1100–1250px</span>, até 900px de
|
||||
largura.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="absolute left-[8%] top-[16%] rounded bg-(--pt-yellow) px-[3%] py-[1.5%] text-[2.7cqw] font-black"
|
||||
>
|
||||
MIOLO ÚTIL ~1080 × 1420
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-black/10 bg-(--campaign-cream) p-6 md:p-8">
|
||||
<h3 class="text-lg font-semibold">Regras que o compositor não pode quebrar</h3>
|
||||
<ol class="mt-5 space-y-5">
|
||||
<li class="flex gap-4">
|
||||
<span
|
||||
class="grid size-8 shrink-0 place-items-center rounded-full bg-(--pt-red) text-sm font-bold text-white"
|
||||
>1</span
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Gancho abaixo do topo crítico</p>
|
||||
<p class="mt-1 text-sm leading-6 text-muted-foreground">
|
||||
A primeira linha começa depois de ~250px. Marca pode encostar mais na borda
|
||||
apenas como elemento redundante.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-4">
|
||||
<span
|
||||
class="grid size-8 shrink-0 place-items-center rounded-full bg-(--pt-red) text-sm font-bold text-white"
|
||||
>2</span
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Legenda curta, central e queimada</p>
|
||||
<p class="mt-1 text-sm leading-6 text-muted-foreground">
|
||||
Uma ideia por bloco, até duas linhas, peso alto, branco sobre preto ≥86%.
|
||||
Palavras de ação recebem amarelo.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-4">
|
||||
<span
|
||||
class="grid size-8 shrink-0 place-items-center rounded-full bg-(--pt-red) text-sm font-bold text-white"
|
||||
>3</span
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Alvo visível antes do clique</p>
|
||||
<p class="mt-1 text-sm leading-6 text-muted-foreground">
|
||||
Cursor sintético + halo amarelo. O zoom amplia a ação, sem cortar o elemento
|
||||
clicado nem desalinhá-lo.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex gap-4">
|
||||
<span
|
||||
class="grid size-8 shrink-0 place-items-center rounded-full bg-(--pt-red) text-sm font-bold text-white"
|
||||
>4</span
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Rodapé não carrega instrução crítica</p>
|
||||
<p class="mt-1 text-sm leading-6 text-muted-foreground">
|
||||
A base conservadora admite apenas marca redundante. CTA e resultado ficam acima
|
||||
dela.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="mt-6 rounded-xl border border-(--pt-red)/25 bg-white p-4">
|
||||
<p class="text-xs font-bold text-(--pt-red)">NEEDS ASSET</p>
|
||||
<p class="mt-1 text-sm leading-6 text-muted-foreground">
|
||||
Captura real do site em viewport mobile, logo oficial transparente e, se aprovada no
|
||||
futuro, foto oficial. Nenhum desses ativos foi inventado neste gate.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CAPA -->
|
||||
<section id="capa" class="mt-20" aria-labelledby="capa-title">
|
||||
<div class="mb-5">
|
||||
<p class="text-xs font-semibold text-(--pt-red)">CENA 05 · CAPA</p>
|
||||
<h2 id="capa-title" class="mt-1 text-xl font-semibold tracking-tight">
|
||||
Título no núcleo que todos os recortes preservam
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Composição própria — não um frame aleatório do vídeo.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-8 xl:grid-cols-[400px_minmax(0,1fr)]">
|
||||
<div
|
||||
class="frame frame-shadow mx-auto w-full max-w-[400px] rounded-[22px] border border-black/15 bg-(--pt-red)"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-0 bg-[radial-gradient(circle_at_80%_25%,rgb(255_230_7/30%),transparent_22%),linear-gradient(160deg,#a21c1c,#47130e)]"
|
||||
></div>
|
||||
<div
|
||||
class="absolute -right-[9%] top-[18%] size-[48%] rotate-12 rounded-[30%] border-[14px] border-(--pt-yellow)"
|
||||
></div>
|
||||
<!-- central 1080x1350 = 70.3125% height, centered -->
|
||||
<div
|
||||
class="absolute inset-x-0 top-[14.84375%] h-[70.3125%] border-y-2 border-dashed crop-line"
|
||||
></div>
|
||||
<div class="absolute left-[7%] right-[7%] top-[22%]">
|
||||
<span
|
||||
class="inline-flex rounded-full bg-(--pt-yellow) px-[4%] py-[1.5%] text-[3cqw] font-black text-black"
|
||||
>TUTORIAL DO SITE</span
|
||||
>
|
||||
<h3 class="brexter mt-[7%] text-[12cqw] leading-[0.88] uppercase text-white">
|
||||
Crie seu<br />card de<br /><span class="text-(--pt-yellow)">apoio.</span>
|
||||
</h3>
|
||||
<p class="mt-[6%] max-w-[65%] text-[3.7cqw] font-semibold leading-[1.2] text-white">
|
||||
Seu nome ou sua foto, direto pelo celular.
|
||||
</p>
|
||||
</div>
|
||||
<div class="absolute inset-x-[7%] bottom-[18%] flex items-end justify-between">
|
||||
<p class="rounded bg-black/40 px-[3%] py-[1.5%] text-[2.4cqw] text-white">#cards</p>
|
||||
<div class="text-right">
|
||||
<p class="logo-lockup brexter text-[7cqw] leading-none text-white">SOLLA</p>
|
||||
<p class="text-[2.4cqw] font-black text-(--pt-yellow)">13 13</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute left-[2%] top-[15.5%] rounded bg-white px-2 py-1 text-[2.5cqw] font-bold text-black"
|
||||
>
|
||||
RECORTE CENTRAL ~1080 × 1350
|
||||
</div>
|
||||
<span
|
||||
class="absolute bottom-[4%] left-[7%] rounded bg-black/55 px-2 py-1 text-[2.5cqw] text-white"
|
||||
>NEEDS ASSET · logo oficial</span
|
||||
>
|
||||
</div>
|
||||
<div class="grid content-start gap-5 md:grid-cols-2">
|
||||
<article class="rounded-2xl border border-black/10 bg-white p-5">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Feed 4:5</p>
|
||||
<p class="text-xs text-muted-foreground">1080 × 1350</p>
|
||||
</div>
|
||||
<span class="rounded-full bg-(--campaign-band) px-2 py-1 text-[10px] font-bold"
|
||||
>SOBREVIVE</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="crop-preview relative mx-auto aspect-[4/5] max-w-[270px] overflow-hidden rounded-xl bg-[linear-gradient(155deg,#a21c1c,#47130e)] p-[7%] text-white"
|
||||
>
|
||||
<span
|
||||
class="inline-flex rounded-full bg-(--pt-yellow) px-[4%] py-[2%] text-[2.3cqw] font-black text-black"
|
||||
>TUTORIAL DO SITE</span
|
||||
>
|
||||
<p class="brexter mt-[9%] text-[9cqw] leading-[0.88] uppercase">
|
||||
Crie seu<br />card de<br /><span class="text-(--pt-yellow)">apoio.</span>
|
||||
</p>
|
||||
<p class="absolute bottom-[8%] right-[7%] logo-lockup brexter text-[5.5cqw]">
|
||||
SOLLA
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-3 text-xs leading-5 text-muted-foreground">
|
||||
Eyebrow, título inteiro e marca continuam visíveis. Copy auxiliar pode sair.
|
||||
</p>
|
||||
</article>
|
||||
<article class="rounded-2xl border border-black/10 bg-white p-5">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Grade 1:1 / 3:4</p>
|
||||
<p class="text-xs text-muted-foreground">recorte central</p>
|
||||
</div>
|
||||
<span class="rounded-full bg-(--campaign-band) px-2 py-1 text-[10px] font-bold"
|
||||
>SOBREVIVE</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="crop-preview relative mx-auto aspect-square max-w-[270px] overflow-hidden rounded-xl bg-[linear-gradient(155deg,#a21c1c,#47130e)] p-[7%] text-white"
|
||||
>
|
||||
<span
|
||||
class="inline-flex rounded-full bg-(--pt-yellow) px-[4%] py-[2%] text-[2.3cqw] font-black text-black"
|
||||
>TUTORIAL</span
|
||||
>
|
||||
<p class="brexter mt-[9%] text-[9cqw] leading-[0.88] uppercase">
|
||||
Crie seu<br />card de<br /><span class="text-(--pt-yellow)">apoio.</span>
|
||||
</p>
|
||||
<p class="absolute bottom-[7%] right-[7%] logo-lockup brexter text-[5.5cqw]">
|
||||
SOLLA
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-3 text-xs leading-5 text-muted-foreground">
|
||||
Título nunca depende do terço inferior. A marca reduz, mas não desaparece.
|
||||
</p>
|
||||
</article>
|
||||
<div
|
||||
class="md:col-span-2 rounded-xl border border-black/10 bg-(--campaign-cream) p-4 text-sm leading-6"
|
||||
>
|
||||
<strong>Hierarquia da capa:</strong> “Crie seu card de apoio” é a mensagem; “Tutorial
|
||||
do site” só categoriza. Não usar screenshot legível como fundo — vira ruído na grade.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SISTEMA -->
|
||||
<section id="spec" class="mt-20" aria-labelledby="spec-title">
|
||||
<div class="mb-5">
|
||||
<p class="text-xs font-semibold text-(--pt-red)">CENA 06 · SISTEMA VISUAL</p>
|
||||
<h2 id="spec-title" class="mt-1 text-xl font-semibold tracking-tight">
|
||||
Receita consistente, não vinheta pesada
|
||||
</h2>
|
||||
</div>
|
||||
<div class="grid gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<article class="rounded-2xl border border-black/10 bg-white p-5">
|
||||
<p class="text-xs font-bold text-(--pt-red)">TIPOGRAFIA</p>
|
||||
<p class="brexter mt-4 text-4xl leading-[0.9] uppercase">Texto de<br />impacto.</p>
|
||||
<p class="mt-4 text-sm leading-6 text-muted-foreground">
|
||||
Brexter Bold só em hook, capa e CTA final. Inter no badge de passo e legendas.
|
||||
</p>
|
||||
</article>
|
||||
<article class="rounded-2xl border border-black/10 bg-white p-5">
|
||||
<p class="text-xs font-bold text-(--pt-red)">PALETA</p>
|
||||
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||
<div
|
||||
class="aspect-[3/2] rounded-lg bg-(--pt-red) p-2 text-[10px] font-bold text-white"
|
||||
>
|
||||
#A21C1C
|
||||
</div>
|
||||
<div class="aspect-[3/2] rounded-lg bg-(--pt-yellow) p-2 text-[10px] font-bold">
|
||||
#FFE607
|
||||
</div>
|
||||
<div
|
||||
class="aspect-[3/2] rounded-lg bg-(--campaign-cream) p-2 text-[10px] font-bold ring-1 ring-black/10"
|
||||
>
|
||||
CREME
|
||||
</div>
|
||||
<div
|
||||
class="aspect-[3/2] rounded-lg bg-(--pt-red-dark) p-2 text-[10px] font-bold text-white"
|
||||
>
|
||||
#47130E
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm leading-6 text-muted-foreground">
|
||||
Amarelo sinaliza ação; nunca vira fundo longo de legenda.
|
||||
</p>
|
||||
</article>
|
||||
<article class="rounded-2xl border border-black/10 bg-white p-5">
|
||||
<p class="text-xs font-bold text-(--pt-red)">LEGENDA</p>
|
||||
<div class="mt-4 rounded-xl bg-stone-400 p-5">
|
||||
<p
|
||||
class="rounded-lg bg-black/90 px-4 py-3 text-center text-base font-bold leading-tight text-white"
|
||||
>
|
||||
Toque em <span class="text-(--pt-yellow)">“Card com seu nome”</span>.
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-4 text-sm leading-6 text-muted-foreground">
|
||||
Máx. 2 linhas · largura ≤900px · centro em 1100–1250px.
|
||||
</p>
|
||||
</article>
|
||||
<article class="rounded-2xl border border-black/10 bg-white p-5">
|
||||
<p class="text-xs font-bold text-(--pt-red)">GESTO</p>
|
||||
<div class="relative mt-4 aspect-[3/2] overflow-hidden rounded-xl bg-stone-300">
|
||||
<div
|
||||
class="absolute left-[18%] top-[18%] size-[58%] rounded-xl border-[3px] border-(--pt-yellow) shadow-[0_0_0_6px_rgb(255_230_7/22%)]"
|
||||
></div>
|
||||
<svg
|
||||
class="cursor absolute left-[61%] top-[56%] w-[12%] text-white"
|
||||
viewBox="0 0 24 30"
|
||||
fill="currentColor"
|
||||
stroke="#000"
|
||||
stroke-width="1.4"
|
||||
>
|
||||
<path d="M3 2v22l6-6 4 9 4-2-4-9h8L3 2Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-4 text-sm leading-6 text-muted-foreground">
|
||||
Zoom 108–122%; cursor chega antes; halo pulsa uma vez; easing suave.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="mt-16 border-t border-black/15 pt-6 text-xs leading-5 text-muted-foreground">
|
||||
<p>
|
||||
<strong class="text-foreground">NEEDS ASSET:</strong> captura real do fluxo `#cards`, logo
|
||||
oficial transparente e qualquer foto oficial aprovada. A composição usa placeholders
|
||||
explícitos.
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
<strong class="text-foreground">Fonte:</strong> o port deve carregar o Brexter Bold
|
||||
oficial. Este HTML autossuficiente usa uma fallback condensada apenas quando Brexter não
|
||||
estiver instalada localmente.
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
<strong class="text-foreground">Em aberto no gate:</strong> confirmar composição própria
|
||||
da capa (recomendação B do plano) e aprovar a frase do hook “Seu apoio vira card em
|
||||
segundos”.
|
||||
</p>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
116
docs/plans/reels-tutoriais.md
Normal file
116
docs/plans/reels-tutoriais.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Skill de geração de reels: captura + composição do vídeo
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-18
|
||||
Issue: #1155
|
||||
Priority: P2
|
||||
Impeccable: C — fluxo novo (skill de geração + template visual do reel); não é UI de app, mas a superfície gráfica do vídeo é o produto
|
||||
Design UI: docs/plans/reels-tutoriais-ui-design.html (a produzir pelo `designer`) + assets
|
||||
Appetite: ~3–4 dias eng; um outcome verificável — `/reels-tutoriais cards` entrega um MP4 publicável
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O mandato precisa de Reels ensinando funcionalidades do site — o primeiro sobre os cards de apoio (`jorgesolla1313.com.br/#cards`). Este item cria o motor de vídeo da skill: a partir de um shot list (fonte única da verdade), a skill dirige o site real em emulação de celular, captura o fluxo e compõe um MP4 vertical 1080×1920 com legenda queimada, zoom suave nos cliques, cursor sintético, marca da campanha e área segura do Instagram. Renderizado, mostra o resultado e abre o gate: aprovar (o pacote segue para a biblioteca privada, via C195) ou pedir ajustes em linguagem natural — a skill edita o shot list e regenera, sem editar o arquivo renderizado. O vídeo sozinho já ajuda; narração vem depois (C197). Nada é publicado em rede social por este item.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** operador de comunicação da campanha, não-técnico, que precisa de vídeos curtos e publicáveis ensinando o site, sem editor de vídeo e sem terceiros.
|
||||
- **Job principal:** numa invocação, obter um reel vertical publicável do tutorial de uma funcionalidade do site, e poder ajustá-lo conversando.
|
||||
- **Fluxo desejado:** `/reels-tutoriais cards` → skill lê/escreve o shot list → dirige o site real em viewport de celular → captura cenas + log de cliques → compõe o MP4 → mostra `reel.mp4` + `capa.png` → gate: publicar na biblioteca (C195) ou ajustar em linguagem natural → ajuste edita o shot list e regenera.
|
||||
- **Anti-goals de produto:** não é editor de vídeo; não publica em rede social; não edita o render (só o shot list); não cobre todas as funcionalidades do site de uma vez; não persiste PII de terceiros nas fixtures.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
/reels-tutoriais cards
|
||||
└─ shot list (cenas, textos, passos) ── fonte única da verdade
|
||||
└─ automação mobile dirige o site real
|
||||
└─ captura do fluxo + log de cliques (zoom/cursor)
|
||||
└─ composição 1080×1920, 30fps, H.264
|
||||
└─ legenda queimada + marca + área segura
|
||||
└─ mostra reel.mp4 + capa.png
|
||||
├─ aprovar → pacote à biblioteca privada (C195)
|
||||
└─ ajustar (linguagem natural)
|
||||
└─ edita shot list → regenera ─┘
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/reels-tutoriais-ui-design.html`
|
||||
- A superfície gráfica do vídeo (template de legenda, cursor, zoom, capa) é o produto: o `designer` entrega o hi-fi antes da implementação, com a paleta do `DESIGN.md` §2 e a tipografia Brexter.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Um humano não-técnico roda `/reels-tutoriais cards` e recebe um pacote com MP4 vertical publicável.
|
||||
- MP4 1080×1920, 30fps, H.264/AAC, ≤3min (alvo 15–60s), legenda queimada legível no mudo.
|
||||
- Área segura respeitada (miolo útil ~1080×1420); nada crítico sob o topo (~14%), a base (~35%) ou as laterais (~6%).
|
||||
- Zoom/câmera suave nos cliques, cursor sintético visível, marca da campanha presente.
|
||||
- Capa `capa.png` sobrevive ao recorte central ~1080×1350 (feed 4:5 e grade do perfil).
|
||||
- Gate: aprovar → pacote à biblioteca privada; ajustar → shot list editado + novo render; nada é publicado no Instagram.
|
||||
- Pacote: `reel.mp4` (sem áudio, primário) + `capa.png` + `metadata.json` (com o hash do shot list); artefatos gitignored.
|
||||
- Fixtures do primeiro reel (nome e foto de exemplo) são fictícias e versionadas; zero PII de terceiros.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — esta entrega é vídeo, não análise; sem gráficos nem métricas.
|
||||
- **Decisões desbloqueadas:** a comunicação decide o que entra no reel e o que muda no ajuste; a coordenação decide quando o tutorial vira peça pública.
|
||||
- **Forma:** _adiada ao plano de implementação_ — aqui só restrições de produto: legenda legível no mudo, área segura do Instagram, determinismo do render.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Skill `.agents/skills/reels-tutoriais/SKILL.md`; command `.opencode/commands/reels-tutoriais.md`; subagente conforme necessidade. Invocação `/reels-tutoriais cards`.
|
||||
- Saída MP4 1080×1920, 30fps, H.264/AAC, ≤3min (alvo 15–60s), miolo útil ~1080×1420, legenda queimada (a maioria assiste no mudo), capa que sobrevive ao ~1080×1350.
|
||||
- Shot list é a fonte única da verdade; ajuste = editar o shot list e regenerar; o MP4 é build determinístico e re-executável.
|
||||
- Gate: aprovar (biblioteca) ou ajustes; nada sobe sem aprovação; nada é publicado no Instagram.
|
||||
- Primeiro reel: funcionalidade `cards` (`#cards`). Pacote: `reel.mp4` (sem áudio) + `capa.png` + `metadata.json`; narração fica para o C197.
|
||||
- Artefatos gitignored (`.mp4` não é coberto hoje — bloco próprio, ex.: `/data/reels/` e `/docs/research/reels/`).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Molde de skill:** `.agents/skills/dossie-solla-cidade/SKILL.md` + command fino em `.opencode/commands/` + subagente em `.opencode/agent/`; pinagem em `tests/unit/opencodeCommands.unit.spec.ts` e `opencodeAgents.unit.spec.ts`.
|
||||
- **Captura:** `chromium` do `@playwright/test` já usado em `scripts/build-dossie-solla-cidade.mjs`/`build-city-report.mjs`, mas navegando o site real em emulação mobile (precedente iOS em `tests/e2e/campaignIosInputZoom.e2e.spec.ts:23-27`); técnica recomendada pela literatura: gravar com o `recordVideo` do contexto headless (o `recordVideo.size` deve ser igual ao viewport), cursor sintético injetado (headless não desenha cursor) e zoom de câmera **em pós-processo** a partir de um log de cliques (não `transform` no DOM — apps Next com portais ignoram).
|
||||
- **Âncoras do primeiro reel:** `section#cards` (`CampaignCardsSection.tsx`), `[data-card-model-tile=...]` (`CardModelGallery.tsx`) e textos do `CardComposer.tsx`; contrato e2e em `tests/e2e/frontend.e2e.spec.ts:1694-1760`.
|
||||
- **Composição:** ffmpeg real é pré-requisito de ambiente (o do Playwright é stripped, sem libx264/drawtext); referência de `FFMPEG_PATH`/timeout em `src/utilities/speech/speechMediaPipeline.ts` — o pipeline do reel roda na workstation.
|
||||
- **Visual:** `DESIGN.md` §2 + Brexter (precedente C191, "a superfície é o gráfico"); bloqueio de artefatos no `.gitignore` como os blocos `/data/*` existentes.
|
||||
- **Risco de acoplamento:** "edite o dono, não gema um irmão" — a skill reusa os builders/utilitários locais existentes; não duplicar pipeline de render; não tocar no site público.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C195 (duro, no fim do fluxo):** a biblioteca privada é o destino do pacote aprovado; sem ele o pacote fica no disco.
|
||||
- **C197 (soft):** narração/transcrição completam o pacote; este item nasce mudo.
|
||||
- **ffmpeg real na workstation** (pré-requisito de ambiente, documentado).
|
||||
- **`designer`:** `docs/plans/reels-tutoriais-ui-design.html` + assets.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Narração/voz/trilha (C197) e qualquer áudio.
|
||||
- Publicação no Instagram ou em qualquer rede social.
|
||||
- Funcionalidades além de `cards` (a skill é genérica; o reel de `cards` fecha o contrato).
|
||||
- Editar o MP4 renderizado; ajustes passam só pelo shot list.
|
||||
- Idiomas além de pt-BR.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Codec/bitrate perfeitos antes de um reel assistível.** **Corte neste item:** entregar o primeiro reel aprovado; refino de encoding depois.
|
||||
- **Zoom via `transform` no DOM.** Apps Next com portais ignoram; quebra o alinhamento do cursor. **Corte neste item:** câmera em pós-processo dirigida por log de cliques.
|
||||
- **Editar o render para "ajustar rápido".** Quebra o determinismo e o gate. **Corte neste item:** ajuste é sempre no shot list.
|
||||
- **Áudio como formato primário.** A maioria assiste no mudo. **Corte neste item:** legenda queimada é o primário; voz é rascunho (C197).
|
||||
- **Cobrir todas as funcionalidades do site.** **Corte neste item:** fechar UM reel de ponta a ponta primeiro.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Como o humano vê/edita o shot list no gate sem virar tarefa técnica?** **Opções:** A) só linguagem natural ("hook mais curto") e a skill traduz | B) abrir o arquivo do shot list para edição direta. **Recomendação:** A no primeiro ciclo, com a skill mostrando o shot list resultante para conferência. _(assumido — validar com produto)_
|
||||
- **Capa: frame do vídeo ou composição própria?** **Opções:** A) frame do vídeo | B) composição própria desenhada no template. **Recomendação:** B — a capa precisa sobreviver aos recortes 4:5 e 1:1/3:4 e carregar o título. _(assumido)_
|
||||
- **Quão determinístico o render precisa ser entre máquinas?** **Opções:** A) determinístico na mesma máquina (basta para o aceite) | B) bit-a-bit entre máquinas. **Recomendação:** A — o hash do shot list identifica a versão do roteiro; reprodutibilidade bit-a-bit não é exigência de produto. _(assumido)_
|
||||
- **Limite de ajustes por sessão?** **Opções:** A) sem limite formal | B) teto declarado para não virar loop. **Recomendação:** A, com a skill sempre mostrando o que mudou; o teto é conversa, não regra.
|
||||
|
||||
## Referências
|
||||
|
||||
- `.agents/skills/dossie-solla-cidade/SKILL.md`; `.opencode/commands/dossie-solla-cidade.md`; `.opencode/agent/dossie-solla-cidade.md` (molde de skill/command/subagente).
|
||||
- `tests/e2e/frontend.e2e.spec.ts:1694-1760` (contrato do `#cards`); `tests/e2e/campaignIosInputZoom.e2e.spec.ts:23-27` (emulação iOS).
|
||||
- `src/app/(frontend)/(home)/CampaignCardsSection.tsx`, `CardModelGallery.tsx`, `CardComposer.tsx`.
|
||||
- `DESIGN.md` §2 (paleta/regras) e tipografia Brexter.
|
||||
- Especificação de Reels (pesquisa 2026-09-18): 1080×1920, 30fps, H.264/AAC, <1GB; safe zone topo ~14% (~250px), base ~35%, laterais ~6%; legenda a ~1100–1250px do topo, ≤900px de largura; feed 4:5 e grade 1:1/3:4; retenção/loops/DM; watermark penalizado.
|
||||
- Itens relacionados: C191 (superfície gráfica como produto), C194 (biblioteca), C195 (ingestão), C197 (narração).
|
||||
- Design UI (gate): `docs/plans/reels-tutoriais-ui-design.html` + assets em `docs/plans/reels-tutoriais-ui-design-assets/`.
|
||||
Loading…
Reference in a new issue