mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
docs(C231–C234): planos + designs hi-fi do acervo de fotos do Flickr (Related #1366, #1367, #1368, #1369)
This commit is contained in:
parent
fe3b0c3215
commit
955715d959
6 changed files with 1765 additions and 0 deletions
109
docs/plans/acervo-fotos-catalogacao-ia.md
Normal file
109
docs/plans/acervo-fotos-catalogacao-ia.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# C232 — Catalogação com IA do acervo de fotos
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-26
|
||||
Issue: #1367
|
||||
Priority: P2
|
||||
Impeccable: B — encaixe de curadoria na ficha/lista existentes do admin/Central, sem rota pública nova; se o gate pedir superfície de revisão própria, o design nasce no gate (HTML abaixo)
|
||||
Design UI: N/A — superfície admin (ficha/lista existentes); design próprio só se o gate pedir: `docs/plans/acervo-fotos-catalogacao-ia-ui-design.html`
|
||||
Appetite: ~2 dias eng + tempo de processamento em lote (6,5k fotos)
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O acervo de fotos entrou para dentro de casa (C231) e virou uma pilha de 6,5k imagens sem ficha: achar a foto da plenária em Vitória da Conquista ou do Solla com uma liderança em Feira é garimpo manual. Cada foto precisa virar encontrável — legenda, atividade, local, pessoas públicas presentes, texto de faixas/placas (OCR) e descrição semântica. A IA pré-cataloga em lote; a curadoria humana revisa e vence a IA; nome de pessoa nunca é inventado de rosto. Sem isso, o álbum público (C233) não tem por onde buscar.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação (`communicator`; `coordinator`/`candidate` também) na mesa, diante de um lote de milhares de fotos e do pouco tempo de revisão.
|
||||
- **Job principal:** transformar o acervo bruto em acervo encontrável — conferir e corrigir a pré-catalogação da IA, sem revisar tudo do zero.
|
||||
- **Fluxo desejado:** o lote é catalogado → cada foto ganha ficha pré-catalogada (legenda, atividade/cena, local, pessoas, texto visível, temas, descrição semântica) → a assessoria abre a lista/ficha, revisa e corrige o que importa → a correção permanece (a IA não sobrescreve) → encontra a foto por local/data/atividade/pessoa/termo → recibo honesto do lote ao fim.
|
||||
- **Anti-goals de produto:** não é o álbum público (C233) nem a busca facial (C234); não inventa nome de pessoa; não expõe nada ao público; não cria segundo cadastro de pessoa; não vira CMS de fotos com edição de imagem.
|
||||
|
||||
### Esboço de fluxo (B)
|
||||
|
||||
```text
|
||||
[acervo C231] → passe de catalogação em lote (IA)
|
||||
→ por foto: legenda/descrição · atividade/cena · local (gazetteer) · pessoas públicas (catálogo curado) · texto visível (OCR) · temas · descrição semântica
|
||||
→ ficha pré-catalogada revisável na lista/ficha existentes
|
||||
→ assessoria revisa e corrige → correção vence a IA
|
||||
→ busca interna por local/data/atividade/pessoa/termo
|
||||
→ recibo do lote: processadas · puladas · falharam com motivo (reexecutável, sem duplicar)
|
||||
[outcome: 6,5k fotos encontráveis — sem nome inventado e sem nada publicado]
|
||||
```
|
||||
|
||||
### Design UI (B)
|
||||
|
||||
- Design UI: N/A — a curadoria acontece na ficha/lista existentes (superfície admin); não há tela nova a desenhar. Se o gate concluir que a revisão do lote precisa de fila/galeria própria, o design nasce no gate em `docs/plans/acervo-fotos-catalogacao-ia-ui-design.html`.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Toda foto do acervo ganha ficha pré-catalogada revisável (ou estado honesto de "nada a propor"), com: legenda/descrição pt-BR, atividade/cena, local, pessoas públicas, texto visível, temas e descrição semântica.
|
||||
- A busca interna por local, data, atividade, pessoa pública e termo funciona sobre o catálogo — e nada disso é exposto ao público.
|
||||
- Curadoria vence a IA: reexecutar o passe nunca sobrescreve campo já editado por humano.
|
||||
- O passe é em lote, não interativo: falha de uma foto não derruba o lote; ao fim há recibo honesto "processadas / puladas / falharam com motivo"; reexecutar não duplica nem reprocessa o que já está catalogado.
|
||||
- Nenhum nome de pessoa é inferido de rosto; pessoas só do catálogo curado e com confirmação humana. Rosto, quando marcado, é presença (tag de cena) — sem identificação biométrica.
|
||||
- **Guardrails:** acervo interno (gate de leitura coerente com a Central); PII/rostos de cidadãos não vão a terceiro sem decisão explícita e contrato; provider reservado aos agentes de design não entra no app.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — catalogação não é métrica; o recibo do lote (processadas/puladas/falharam) é operacional, não agregado de leitura.
|
||||
- **Decisões desbloqueadas:** N/A — a decisão da assessoria é editorial (revisar/corrigir/usar a foto), não numérica.
|
||||
- **Forma:** adiada ao plano de implementação — restrição de produto: nenhum score/confiança numérico exposto na ficha; o recibo do lote é só contagem honesta.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Por foto, a IA deve propor: **legenda/descrição pt-BR**, **atividade/cena**, **local** (município pelo gazetteer existente; Salvador/zona ambíguo → null), **pessoas públicas** (só do catálogo curado `publicFigureCatalog` — nunca inferir nome de rosto sem confirmação humana), **texto visível** (OCR de faixas/placas/banners), **tags/temas** do vocabulário existente e **descrição semântica/embedding** para busca futura.
|
||||
- **Curadoria vence a IA:** campo já curado por humano nunca é sobrescrito (precedente `curatedFields`).
|
||||
- **Processamento em lote, não interativo**: falha por foto não derruba o lote; recibo honesto "processadas / puladas / falharam com motivo"; reexecução idempotente por foto.
|
||||
- **Face/rosto**: nesta fatia, no máximo detectar **presença de pessoa/rosto** como tag de cena — **sem identificação biométrica** (isso é C234). Nome de pessoa só por catálogo curado e revisão humana.
|
||||
- **Engine de visão a decidir na implementação** (não fixar fornecedor aqui): opções local/self-hosted vs API de terceiro; restrição de produto: PII/rostos de cidadãos não vão para terceiro sem decisão explícita e contrato; provider reservado aos agentes de design não entra no app.
|
||||
- Curadoria/edição por assessoria no admin, com gate de leitura coerente com a Central de Conteúdos.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** catalogação `src/utilities/content/` (C230 já usa esse pipeline — reusar, não duplicar engine); job/agendador sem cron como `contentPieceJob.ts`/`contentPieceScheduler.ts`; `src/utilities/ai/` (chamadas com timeout e fallback); `src/lib/speechFacets.ts` (temas), `src/lib/publicFigureCatalog.ts` (pessoas), `src/lib/speechGazetteer.ts` (municípios); access `src/utilities/access/contentPieces.ts` + barrel; grupo admin `'Comunicação'`; migration via `pnpm migrate:create` (`push:false`).
|
||||
- **Precedente a olhar:** C211/C220/C230 (pipeline, ficha e curadoria que vence IA), C226 (passo que não derruba o job), C153 (classificador + gazetteer), C231 (dono do acervo — shape fica lá).
|
||||
- **Risco de acoplamento:** não criar segundo engine de catalogação; `curatedFields` é o mecanismo de precedência; `src/utilities/ai` e `src/lib/schemas` são prefixes de risco fail-closed no CI (pins `contentPieceCataloging.unit.spec.ts`, `expandSpeechSearchTheme.unit.spec.ts`); `openai/*` fica fora do app (OPS123).
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Dura:** C231 — sem o acervo dentro de casa não há lote nem ficha.
|
||||
- **Soft:** C211/C220/C230 (pipeline e precedentes de curadoria), C153 (classificador), C226 (job isolado), C229 (embedding restrito ao acervo de falas como precedente).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Álbum público (C233) e busca facial/identificação biométrica (C234).
|
||||
- Vídeos, saneamento/tratamento de imagem, analytics de acervo.
|
||||
- Publicação de qualquer foto; edição/recorte de imagem; segundo cadastro de pessoa.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Só completar" rotulando cada rosto manualmente.** Se alguém "só completar": revisão identidade a identidade de 6,5k fotos = semana de trabalho. **Corte neste item:** a IA pré-cataloga; o humano revisa por exceção; rosto é tag de cena (identidade é C234).
|
||||
- **Reescrever a taxonomia de temas.** Se alguém "só completar": nova ontologia para o acervo. **Corte neste item:** vocabulário existente (`speechFacets`).
|
||||
- **API de visão de terceiro sem decisão de PII.** Se alguém "só completar": manda foto de cidadão para fora "para testar". **Corte neste item:** engine decidido na implementação sob a restrição de produto; sem decisão explícita e contrato, nada de PII para terceiro.
|
||||
- **Catalogar tudo com humano em vez de IA.** Se alguém "só completar": descrição manual foto a foto. **Corte neste item:** lote com IA + curadoria por exceção.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Engine de visão?** **Opções:** A) local/self-hosted | B) API de terceiro | C) híbrido (local para tudo sensível). **Recomendação:** A — o lote não é interativo e roda em casa; PII/rostos não saem sem contrato; se a qualidade não fechar o aceite, reabre com B/C e decisão explícita de PII. _(assumido — validar com produto)_
|
||||
- **Quanto de curadoria humana por foto no v1?** **Opções:** A) amostragem | B) 100% revisado | C) por exceção (baixa confiança + o que for usado). **Recomendação:** C — 100% de 6,5k é semana de trabalho e amostragem não protege o que importa; campo de pessoa sempre passa por confirmação humana. _(assumido — validar com produto)_
|
||||
- **Foto sem metadado de local (geotag raro)?** **Opções:** A) gazetteer só pelo texto de álbum/título/OCR | B) não tentar (null) | C) pedir local à assessoria. **Recomendação:** A — tentar pelo texto disponível; sem sinal, null honesto; nunca chutar. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue [#1367](https://github.com/fsolla/teqo/issues/1367)
|
||||
- Design UI: N/A — superfície admin; design próprio só se o gate pedir: `docs/plans/acervo-fotos-catalogacao-ia-ui-design.html`.
|
||||
- Planos irmãos: C231 (dependência dura) · `central-conteudos-importar-perfil.md` (C230) · `central-conteudos-link-instagram.md` (C220) · `central-conteudos-ingestao.md` (C211) · `acervo-busca-por-sentido.md` (C229).
|
||||
- Arquivos-pista: `src/utilities/content/contentPieceCataloging.ts` · `contentPieceJob.ts` · `contentPieceScheduler.ts` · `src/collections/ContentPiece.ts` (`curatedFields`) · `src/lib/publicFigureCatalog.ts` · `src/lib/speechFacets.ts` · `src/lib/speechGazetteer.ts` · `src/utilities/ai/themeSearchGuard.ts` · `rateLimit.ts` · `scripts/lib/e2e-affected-manifest.mjs`.
|
||||
- `AGENTS.md` — convenções do repo (sem segundo cadastro de pessoa; PII; provider reservado aos agentes de design).
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
1. Fatia = um outcome verificável? **Sim** — 6,5k fotos pré-catalogadas, buscáveis e com curadoria que permanece.
|
||||
2. Appetite declarado e a intenção cabe? **Sim** (~2 dias eng + lote; fila de revisão/UI nova, treino de modelo e terceiro sem decisão cortados).
|
||||
3. Persona + job + aceite claros sem jargão de stack? **Sim**.
|
||||
4. Direção no codebase é hipótese? **Sim** — áreas e precedentes, sem schema/signature/fornecedor.
|
||||
5. Zero decisões duras de engenharia no plano? **Sim** — engine, storage da descrição semântica, shape dos campos e migration ficam no plano de implementação.
|
||||
|
||||
**Score: 5/5.**
|
||||
91
docs/plans/acervo-fotos-flickr-ingestao.md
Normal file
91
docs/plans/acervo-fotos-flickr-ingestao.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# C231 — Acervo de fotos do Flickr — ingestão completa
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-26
|
||||
Issue: #1366
|
||||
Priority: P2
|
||||
Impeccable: A — N/A sem UI
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~1 dia eng (script + inventário; sem UI); um outcome verificável — as 6.577 fotos originais do Flickr e seus metadados passam a viver no storage do Teqo, reexecutável sem duplicar e com recibo honesto
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O acervo de milhares de fotos do Jorge Solla vive só no Flickr — conta própria `depjorgesolla`, 6.577 fotos, criada em 2022 — fora do Teqo: sem storage próprio, sem inventário, refém de uma plataforma de terceiro. Enquanto o acervo de falas já tem esteira e a Central de Conteúdos organiza o que publicar, as fotos não têm onde morar. Antes de catalogar (C232) e publicar (C233/C234), elas precisam estar em casa: originais + metadados (data, álbuns, título/descrição/tags, geotag, EXIF) no storage do Teqo, com inventário conferível e reexecução que não duplica.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenação técnica rodando a ingestão (mesa, uma vez agora e de novo a cada lote novo do Flickr); a consumidora final é a comunicação, que hoje não enxerga o acervo.
|
||||
- **Job principal:** trazer o acervo fotográfico inteiro para o storage do Teqo, sem perder nada, sem duplicar e com um recibo que se possa conferir.
|
||||
- **Fluxo desejado:** roda em dry-run → confere o que entraria → roda com confirmação explícita → originais + metadados descem para o storage → lê o recibo "novas / já existiam / falharam com motivo" e o inventário → reexecuta quando quiser, convergindo.
|
||||
- **Anti-goals de produto:** não é catálogo curado nem galeria; não publica nada; não edita/saneia foto; não cria cadastro de pessoa; não altera nada no Flickr.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- O acervo do Flickr (`depjorgesolla`) fica recuperável e inventariável no storage do Teqo: originais + metadados por foto (data, álbum(ns), título/descrição/tags do Flickr, geotag quando houver, EXIF quando houver).
|
||||
- Nada se perde: cada foto termina em um de dois estados honestos — arquivada ou falha nomeada no recibo; falha nunca em silêncio e nenhum arquivo órfão.
|
||||
- Reexecutar é seguro: a identidade é o id da foto no Flickr; rodar de novo não duplica, converge e reporta "novas / já existiam / falharam com motivo".
|
||||
- O inventário responde sozinho "quantas vieram, quantas já existiam, quais falharam e por quê" sem abrir o Flickr.
|
||||
- Guardrails: dry-run por padrão; escrever no bucket exige confirmação explícita; a CLI nunca aponta para DB/bucket de produção fora do runbook; nenhuma foto é publicada neste item; nada no Flickr é tocado.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — é operação/ingestão, não analytics: sem agregado, KPI ou superfície nova. O recibo da execução é conferência operacional (novas / já existiam / falharam), não métrica — não desbloqueia decisão de produto e não ganha painel.
|
||||
- **Decisões desbloqueadas:** N/A — a decisão aqui é operacional ("o acervo está em casa? posso reexecutar?"), não numérica.
|
||||
- **Forma:** N/A — sem engajamento/alcance; inventário é contagem de conferência.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Fonte: API oficial do Flickr da conta própria `depjorgesolla` (flickr.com/photos/depjorgesolla) — **6.577 fotos** confirmadas em 2026-09-26; baixar **originais**.
|
||||
- Metadados a preservar por foto: data, álbum(ns), título/descrição/tags do Flickr, geotag quando houver, EXIF quando houver.
|
||||
- Idempotência: identidade = **id da foto no Flickr**; reexecutar não duplica e converge.
|
||||
- Storage: storage S3 do Teqo (bucket privado + proxy) — nunca disco efêmero de prod.
|
||||
- Recibo por execução: "novas / já existiam / falharam com motivo" — falha nunca em silêncio; nenhum arquivo órfão.
|
||||
- Guardas de escrita: dry-run por padrão + confirmação explícita para escrever no bucket (precedente `SEED_MEDIA_CONFIRM`/`MEDIA_RECOVER_CONFIRM`); CLI nunca aponta para DB/bucket de produção fora do runbook.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** script em `scripts/` no padrão dos imports idempotentes (dry-run, confirmação, relatório); storage em `src/utilities/mediaStorage.ts` + padrão das collections de upload privado (`ContentMedia`/`RecordingMedia`), fora do mapa S3 das collections públicas do `payload.config.ts`; download com teto como `downloadToFile.ts`.
|
||||
- **Precedente a olhar:** `scripts/import-web-speeches.mjs` e `scripts/lib/cli.mjs` (dry-run, `assertWriteConfirm`, `ensureCachedDownload` com sha256), `scripts/recover-media.mjs` (`--dry-run/--verify/reconcile` + inventário verificável), `scripts/seed-posts.mjs` (`SEED_MEDIA_CONFIRM`, idempotência por chave), `src/utilities/speech/speechImport.ts` e `src/utilities/reels/reelPackageIngest.ts` (gravação transacional por chave natural).
|
||||
- **Risco de acoplamento:** não criar segundo cadastro de pessoa; não pendurar o acervo na `media` pública (leitura anônima); não herdar o mapa S3 das collections públicas sem revisar visibilidade; egress de rede pelo proxy com allowlist já usado pelos scripts; `push: false` e migração commitada se houver schema novo.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura. C232 (catalogação) e C233/C234 (publicação) dependem deste.
|
||||
- Credenciais da API do Flickr (Q1) e capacidade do bucket do homeserver (Q3) precisam estar resolvidas antes do lote.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Catalogação com IA/tags (C232); publicação (C233/C234); curadoria/edição/saneamento de imagem; álbum público e selfie; vídeos do Flickr e outras plataformas (têm donos próprios); qualquer alteração no Flickr; UI de administração do acervo.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Já que baixa foto, baixa os vídeos do Flickr também".** Se alguém "só completar": formatos de vídeo, streaming, GBs a mais. **Corte neste item:** só fotos; vídeo tem dono próprio.
|
||||
- **"Reorganiza/renomeia tudo no Flickr".** Se alguém "só completar": re-upload, perda de data/álbum, links quebrados. **Corte neste item:** baixar como está; nome/organização é decisão da catalogação (C232).
|
||||
- **"Monta a UI de administração do acervo agora".** Se alguém "só completar": tela, filtros, curadoria — fora do appetite. **Corte neste item:** script + inventário; UI é C232/C233.
|
||||
- **"Apaga do Flickr o que já baixou".** Se alguém "só completar": perda irreversível da fonte. **Corte neste item:** nunca; o Flickr é fonte e não se toca.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Quem fornece as credenciais da API do Flickr e onde elas vivem?** **Opções:** A) chave de API da própria conta, provisionada pelo dono e só no env do runbook (nunca no repo) | B) OAuth do dono executado na hora | C) scraping da página pública. **Recomendação:** A — caminho oficial para conta própria, segredo fora do repo; C é proibido pelo padrão anti-scraping. _(assumido — validar com produto)_
|
||||
- **Fotos que aparecem em múltiplos álbuns duplicam?** **Opções:** A) não — dedupe pelo id já resolve e os álbuns viram metadado da mesma foto | B) uma cópia por álbum. **Recomendação:** A — uma foto, N álbuns no metadado; registrar a contagem no inventário.
|
||||
- **O bucket do homeserver comporta o lote?** **Opções:** A) medir antes (quantidade × tamanho médio dos originais) e só então rodar o lote completo | B) rodar em lotes por álbum. **Recomendação:** A com verificação prévia; se apertar, B é a contingência. _(assumido — verificar antes do lote)_
|
||||
- **Existem fotos de terceiros em álbuns próprios?** **Opções:** A) inventariar com proveniência e não republicar sem revisão | B) excluir da ingestão. **Recomendação:** A — o risco é de publicação, não de arquivo: baixar para preservar e marcar para revisão futura.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue [#1366](https://github.com/fsolla/teqo/issues/1366)
|
||||
- Design UI (gate): N/A — sem UI
|
||||
- Plano irmão: C232 (catalogação do acervo — depende deste); C230 `docs/plans/central-conteudos-importar-perfil.md` (precedente de ingestão sob demanda — janela recente/dedupe por id; aqui é o oposto intencional: backfill em massa da fonte própria).
|
||||
- Arquivos-pista: `src/utilities/mediaStorage.ts` · `src/payload.config.ts` (mapa S3) · `src/collections/ContentMedia.ts` e `src/collections/RecordingMedia.ts` (mídia privada) · `src/utilities/media/downloadToFile.ts` · `scripts/import-web-speeches.mjs` · `scripts/recover-media.mjs` · `scripts/seed-posts.mjs` · `scripts/lib/cli.mjs` · `src/utilities/speech/speechImport.ts` · `src/utilities/reels/reelPackageIngest.ts`
|
||||
- `AGENTS.md` (storage S3, guards de escrita, `push: false`/migrações)
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
1. Fatia = um outcome verificável? **Sim** — 6.577 fotos originais + metadados no storage do Teqo, reexecutável e com recibo.
|
||||
2. Appetite declarado e a intenção cabe? **Sim** (~1 dia, script + inventário; UI/edição/vídeo cortados).
|
||||
3. Persona + job + aceite claros sem jargão de stack? **Sim**.
|
||||
4. Direção no codebase é hipótese? **Sim** — áreas/pistas, sem schema/signature.
|
||||
5. Zero decisões duras de engenharia? **Sim** — collection, chave, formato de inventário e download ficam no plano de implementação.
|
||||
|
||||
**Score: 5/5.**
|
||||
668
docs/plans/album-fotos-publico-busca-ui-design.html
Normal file
668
docs/plans/album-fotos-publico-busca-ui-design.html
Normal file
|
|
@ -0,0 +1,668 @@
|
|||
<!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 — C233 · Álbum público de fotos</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, Arimo, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-card: var(--card);
|
||||
--color-ring: var(--ring);
|
||||
}
|
||||
[data-theme='campaign-site'] {
|
||||
--background: #fff8f2;
|
||||
--foreground: #201b1a;
|
||||
--card: #ffffff;
|
||||
--primary: #e4102f;
|
||||
--primary-foreground: #ffffff;
|
||||
--muted: #efe9e7;
|
||||
--muted-foreground: #6c615e;
|
||||
--border: rgb(32 27 26 / 13%);
|
||||
--ring: #e4102f;
|
||||
--brand-red: #e4102f;
|
||||
--brand-blue: #184e92;
|
||||
--brand-yellow: #ffeb00;
|
||||
--site-red: #ae1603;
|
||||
--cream: #fff8f2;
|
||||
--band: #ebe9e9;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, Arimo, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.display {
|
||||
font-family: 'Exo 2', Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.stage {
|
||||
box-shadow: 0 22px 70px rgb(32 27 26 / 14%);
|
||||
}
|
||||
.focus-ring:focus-visible {
|
||||
outline: 3px solid var(--brand-yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.photo-placeholder {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(145deg, transparent 48%, rgb(255 255 255 / 38%) 49%, transparent 51%),
|
||||
linear-gradient(135deg, #d6d3d1, #a8a29e);
|
||||
}
|
||||
.photo-placeholder::before {
|
||||
content: 'NEEDS ASSET · foto aprovada';
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 1;
|
||||
border-radius: 999px;
|
||||
background: rgb(255 255 255 / 88%);
|
||||
padding: 4px 8px;
|
||||
color: #57534e;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.photo-placeholder::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12%;
|
||||
bottom: -15%;
|
||||
width: 44%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 999px 999px 30% 30%;
|
||||
background: rgb(87 83 78 / 38%);
|
||||
box-shadow: -80px 18px 0 -22px rgb(120 113 108 / 42%);
|
||||
}
|
||||
.photo-b {
|
||||
background: linear-gradient(135deg, #d6d3d1, #78716c);
|
||||
}
|
||||
.photo-c {
|
||||
background: linear-gradient(135deg, #e7e5e4, #a8a29e);
|
||||
}
|
||||
.photo-d {
|
||||
background: linear-gradient(135deg, #cbd5e1, #78716c);
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
min-height: 40px;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: white;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chip-active {
|
||||
border-color: rgb(24 78 146 / 35%);
|
||||
background: #f0f5fb;
|
||||
color: #184e92;
|
||||
}
|
||||
.primary {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--brand-red);
|
||||
padding: 10px 16px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 8px 18px rgb(228 16 47 / 18%);
|
||||
}
|
||||
.secondary {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
padding: 10px 16px;
|
||||
color: #201b1a;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
scroll-behavior: auto !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-[#efeeec] text-foreground antialiased">
|
||||
<svg class="absolute size-0 overflow-hidden" aria-hidden="true">
|
||||
<symbol id="search" viewBox="0 0 24 24">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</symbol>
|
||||
<symbol id="sliders" viewBox="0 0 24 24">
|
||||
<path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3" />
|
||||
<path d="M1 14h6M9 8h6M17 16h6" />
|
||||
</symbol>
|
||||
<symbol id="calendar" viewBox="0 0 24 24">
|
||||
<rect x="3" y="5" width="18" height="16" rx="2" />
|
||||
<path d="M16 3v4M8 3v4M3 11h18" />
|
||||
</symbol>
|
||||
<symbol id="map" viewBox="0 0 24 24">
|
||||
<path d="M20 10c0 5-8 11-8 11S4 15 4 10a8 8 0 1 1 16 0Z" />
|
||||
<circle cx="12" cy="10" r="2.5" />
|
||||
</symbol>
|
||||
<symbol id="user" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="8" r="4" />
|
||||
<path d="M4 21a8 8 0 0 1 16 0" />
|
||||
</symbol>
|
||||
<symbol id="chev" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6" /></symbol>
|
||||
<symbol id="down" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="x" viewBox="0 0 24 24"><path d="m18 6-12 12M6 6l12 12" /></symbol>
|
||||
<symbol id="image" viewBox="0 0 24 24">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
<circle cx="9" cy="9" r="2" />
|
||||
<path d="m21 15-5-5L5 21" />
|
||||
</symbol>
|
||||
<symbol id="arrow" viewBox="0 0 24 24"><path d="m15 18-6-6 6-6" /></symbol>
|
||||
<symbol id="download" viewBox="0 0 24 24"><path d="M12 3v12M7 10l5 5 5-5M5 21h14" /></symbol>
|
||||
</svg>
|
||||
|
||||
<main class="mx-auto flex max-w-[1480px] flex-col gap-16 px-4 py-10 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="rounded-full bg-[#e4102f] px-3 py-1 text-xs font-extrabold text-white"
|
||||
>C233 · gate hi-fi</span
|
||||
>
|
||||
<span class="rounded-full border border-border bg-white px-3 py-1 text-xs font-bold"
|
||||
>Rota pública · /fotos</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="display mt-4 text-3xl font-black tracking-[-0.03em]">
|
||||
Álbum público: encontrar a foto certa sem virar rede social
|
||||
</h1>
|
||||
<p class="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||
Cidadão procura por termo, data, município, atividade ou pessoa pública; vê somente fotos
|
||||
aprovadas e entende o contexto antes de guardar a imagem.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- CENA 01 -->
|
||||
<section aria-labelledby="scene-1">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 01</p>
|
||||
<h2 id="scene-1" class="display mt-1 text-xl font-black">
|
||||
Desktop ~1280 · cabeçalho, busca, facetas e grade contextual
|
||||
</h2>
|
||||
<div class="stage mt-4 overflow-x-auto rounded-2xl border border-border bg-white">
|
||||
<div class="min-h-[1040px] min-w-[1180px] bg-white">
|
||||
<header class="bg-[#ae1603]">
|
||||
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-8">
|
||||
<div class="relative h-[52px] w-[144px] overflow-hidden">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
alt="Jorge Solla"
|
||||
class="absolute top-[-29.5px] left-[-22.4px] h-auto w-[189px] max-w-none"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-sm font-bold text-white">
|
||||
<span>Início</span
|
||||
><span aria-current="page" class="border-b-2 border-[#ffeb00] py-2">Fotos</span
|
||||
><span>Conteúdos</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section
|
||||
class="relative overflow-hidden border-b-4 border-[#ffeb00] bg-[linear-gradient(112deg,#e4102f_0%,#b8102a_58%,#184e92_100%)] text-white"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 w-[38%] bg-[url('../../public/campaign-kit/pattern-shapes.png')] bg-[length:100%_auto] bg-[position:center_5%] bg-no-repeat opacity-[.1]"
|
||||
></div>
|
||||
<div
|
||||
class="relative mx-auto flex min-h-[230px] max-w-6xl items-center justify-between gap-12 px-8 py-10"
|
||||
>
|
||||
<div class="max-w-2xl">
|
||||
<p class="m-0 text-xs font-black tracking-[.12em] text-[#ffeb00] uppercase">
|
||||
Memória da nossa caminhada
|
||||
</p>
|
||||
<h3 class="display mt-3 text-[42px] leading-[1] font-black tracking-[-.03em]">
|
||||
Fotos que contam onde a gente esteve.
|
||||
</h3>
|
||||
<p class="mt-4 max-w-xl text-base leading-6 text-white/90">
|
||||
Procure uma agenda, um município ou uma pessoa pública e abra cada registro com
|
||||
data e contexto.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="secondary relative shrink-0 border-white/40 bg-white/10 text-white"
|
||||
>Encontre você nas fotos <svg class="icon"><use href="#chev" /></svg
|
||||
></a>
|
||||
</div>
|
||||
</section>
|
||||
<div class="mx-auto max-w-6xl px-8 py-8">
|
||||
<div class="rounded-2xl bg-[#fff8f2] p-5">
|
||||
<label class="text-sm font-extrabold" for="desktop-search"
|
||||
>O que você procura?</label
|
||||
>
|
||||
<div class="mt-2 grid grid-cols-[1fr_auto] gap-2">
|
||||
<div
|
||||
class="flex h-12 items-center gap-3 rounded-[10px] border border-black/20 bg-white px-4"
|
||||
>
|
||||
<svg class="icon text-muted-foreground"><use href="#search" /></svg
|
||||
><input
|
||||
id="desktop-search"
|
||||
class="min-w-0 flex-1 bg-transparent text-[15px] outline-none"
|
||||
placeholder="Ex.: plenária de saúde em município"
|
||||
/>
|
||||
</div>
|
||||
<button class="primary min-w-28">Buscar fotos</button>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap gap-2" aria-label="Filtros">
|
||||
<button class="chip">
|
||||
<svg class="icon size-4"><use href="#calendar" /></svg>Data
|
||||
<svg class="icon size-3.5"><use href="#down" /></svg>
|
||||
</button>
|
||||
<button class="chip chip-active">
|
||||
<svg class="icon size-4"><use href="#map" /></svg>Município · selecionado
|
||||
<svg class="icon size-3.5"><use href="#x" /></svg>
|
||||
</button>
|
||||
<button class="chip">
|
||||
Atividade <svg class="icon size-3.5"><use href="#down" /></svg>
|
||||
</button>
|
||||
<button class="chip">
|
||||
Pessoa pública <svg class="icon size-3.5"><use href="#down" /></svg>
|
||||
</button>
|
||||
<button class="ml-auto text-sm font-extrabold text-[#184e92]">
|
||||
Limpar filtros
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 flex items-end justify-between border-b border-border pb-4">
|
||||
<div>
|
||||
<p class="text-xs font-black tracking-[.1em] text-[#e4102f] uppercase">
|
||||
Agenda em destaque
|
||||
</p>
|
||||
<h3 class="display mt-1 text-2xl font-black">
|
||||
Plenárias e encontros no município
|
||||
</h3>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">Mais recentes primeiro</span>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-3 gap-x-4 gap-y-7">
|
||||
<article class="group">
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<div class="pt-3">
|
||||
<p class="display text-base font-black">Plenária pela saúde pública</p>
|
||||
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
12 set 2026 · Município selecionado · Plenária
|
||||
</p>
|
||||
<p class="mt-1 text-sm">Jorge Solla e pessoa pública convidada</p>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder photo-b aspect-[4/3] rounded-xl"></div>
|
||||
<div class="pt-3">
|
||||
<p class="display text-base font-black">Visita à unidade de saúde</p>
|
||||
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
08 set 2026 · Município selecionado · Visita
|
||||
</p>
|
||||
<p class="mt-1 text-sm">Jorge Solla</p>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder photo-c aspect-[4/3] rounded-xl"></div>
|
||||
<div class="pt-3">
|
||||
<p class="display text-base font-black">Encontro com trabalhadores</p>
|
||||
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
03 set 2026 · Município selecionado · Reunião
|
||||
</p>
|
||||
<p class="mt-1 text-sm">Jorge Solla e pessoa pública convidada</p>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder photo-d aspect-[4/3] rounded-xl"></div>
|
||||
<div class="pt-3">
|
||||
<p class="display text-base font-black">Caminhada no centro</p>
|
||||
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
28 ago 2026 · Município selecionado · Caminhada
|
||||
</p>
|
||||
<p class="mt-1 text-sm">Jorge Solla</p>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<div class="pt-3">
|
||||
<p class="display text-base font-black">Conversa sobre educação</p>
|
||||
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
22 ago 2026 · Município selecionado · Encontro
|
||||
</p>
|
||||
<p class="mt-1 text-sm">Jorge Solla e pessoa pública convidada</p>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder photo-b aspect-[4/3] rounded-xl"></div>
|
||||
<div class="pt-3">
|
||||
<p class="display text-base font-black">Reunião com a comunidade</p>
|
||||
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
||||
16 ago 2026 · Município selecionado · Reunião
|
||||
</p>
|
||||
<p class="mt-1 text-sm">Jorge Solla</p>
|
||||
</div>
|
||||
</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 02 -->
|
||||
<section aria-labelledby="scene-2">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 02</p>
|
||||
<h2 id="scene-2" class="display mt-1 text-xl font-black">
|
||||
Mobile ~390 · grade em 2 colunas e filtros em bottom sheet
|
||||
</h2>
|
||||
<div class="mt-4 flex flex-wrap items-start gap-8">
|
||||
<div class="stage w-[390px] overflow-hidden rounded-[30px] border border-border bg-white">
|
||||
<header class="flex h-14 items-center justify-between bg-[#ae1603] px-4 text-white">
|
||||
<div class="relative h-[46px] w-[128px] overflow-hidden">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
alt="Jorge Solla"
|
||||
class="absolute top-[-26.1px] left-[-19.8px] h-auto w-[167px] max-w-none"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-sm font-bold">Fotos</span>
|
||||
</header>
|
||||
<div
|
||||
class="border-b-4 border-[#ffeb00] bg-[linear-gradient(125deg,#e4102f,#184e92)] px-5 py-6 text-white"
|
||||
>
|
||||
<p class="text-[10px] font-black tracking-[.12em] text-[#ffeb00] uppercase">
|
||||
Nossa caminhada
|
||||
</p>
|
||||
<h3 class="display mt-2 text-[30px] leading-[.98] font-black">
|
||||
Encontre um momento.
|
||||
</h3>
|
||||
<p class="mt-2 text-sm leading-5 text-white/85">
|
||||
Busque pela agenda, pelo lugar ou por quem apareceu.
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
class="mt-4 inline-flex min-h-10 items-center gap-2 rounded-lg border border-white/40 px-3 text-sm font-bold"
|
||||
>Encontre você nas fotos <svg class="icon size-4"><use href="#chev" /></svg
|
||||
></a>
|
||||
</div>
|
||||
<div class="p-4 pb-8">
|
||||
<div class="flex h-11 items-center gap-2 rounded-[10px] border border-black/20 px-3">
|
||||
<svg class="icon text-muted-foreground"><use href="#search" /></svg
|
||||
><span class="text-sm text-muted-foreground">Agenda, município ou pessoa…</span>
|
||||
</div>
|
||||
<div class="scrollbar-hide mt-3 flex gap-2 overflow-x-auto pb-1">
|
||||
<button class="chip shrink-0">
|
||||
<svg class="icon size-4"><use href="#sliders" /></svg>Filtros</button
|
||||
><button class="chip chip-active shrink-0">
|
||||
Município <svg class="icon size-3"><use href="#x" /></svg></button
|
||||
><button class="chip shrink-0">Data</button
|
||||
><button class="chip shrink-0">Atividade</button>
|
||||
</div>
|
||||
<div class="mt-5 grid grid-cols-2 gap-x-3 gap-y-5">
|
||||
<article>
|
||||
<div class="photo-placeholder aspect-square rounded-lg"></div>
|
||||
<p class="mt-2 text-sm font-extrabold leading-4">Plenária pela saúde</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-muted-foreground">
|
||||
12 set · Município · Plenária
|
||||
</p>
|
||||
<p class="mt-1 text-[11px] leading-4">Jorge Solla e pessoa convidada</p>
|
||||
</article>
|
||||
<article>
|
||||
<div class="photo-placeholder photo-b aspect-square rounded-lg"></div>
|
||||
<p class="mt-2 text-sm font-extrabold leading-4">Visita à unidade</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-muted-foreground">
|
||||
08 set · Município · Visita
|
||||
</p>
|
||||
<p class="mt-1 text-[11px] leading-4">Jorge Solla</p>
|
||||
</article>
|
||||
<article>
|
||||
<div class="photo-placeholder photo-c aspect-square rounded-lg"></div>
|
||||
<p class="mt-2 text-sm font-extrabold leading-4">Encontro local</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-muted-foreground">
|
||||
03 set · Município · Reunião
|
||||
</p>
|
||||
<p class="mt-1 text-[11px] leading-4">Jorge Solla e pessoa convidada</p>
|
||||
</article>
|
||||
<article>
|
||||
<div class="photo-placeholder photo-d aspect-square rounded-lg"></div>
|
||||
<p class="mt-2 text-sm font-extrabold leading-4">Caminhada no centro</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-muted-foreground">
|
||||
28 ago · Município · Caminhada
|
||||
</p>
|
||||
<p class="mt-1 text-[11px] leading-4">Jorge Solla</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="stage relative w-[390px] overflow-hidden rounded-[30px] border border-border bg-white"
|
||||
>
|
||||
<div
|
||||
class="h-[720px] bg-[linear-gradient(rgb(32_27_26/36%),rgb(32_27_26/36%)),white]"
|
||||
></div>
|
||||
<div
|
||||
class="absolute inset-x-0 bottom-0 rounded-t-[26px] bg-white px-5 pt-4 pb-7 shadow-[0_-16px_44px_rgb(0_0_0/18%)]"
|
||||
>
|
||||
<div class="mx-auto h-1 w-10 rounded-full bg-stone-300"></div>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-black tracking-[.08em] text-[#e4102f] uppercase">
|
||||
Filtros
|
||||
</p>
|
||||
<h3 class="display text-xl font-black">Refine sua busca</h3>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Fechar"
|
||||
class="grid size-10 place-items-center rounded-full bg-muted"
|
||||
>
|
||||
<svg class="icon"><use href="#x" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-5 grid gap-4">
|
||||
<label class="text-sm font-bold"
|
||||
>Data<select
|
||||
class="mt-1.5 h-11 w-full rounded-[10px] border border-black/20 bg-white px-3 font-normal"
|
||||
>
|
||||
<option>Qualquer data</option>
|
||||
</select></label
|
||||
>
|
||||
<label class="text-sm font-bold"
|
||||
>Município<select
|
||||
class="mt-1.5 h-11 w-full rounded-[10px] border border-[#184e92]/45 bg-white px-3 font-normal text-[#184e92]"
|
||||
>
|
||||
<option>Município selecionado</option>
|
||||
</select></label
|
||||
>
|
||||
<label class="text-sm font-bold"
|
||||
>Atividade<select
|
||||
class="mt-1.5 h-11 w-full rounded-[10px] border border-black/20 bg-white px-3 font-normal"
|
||||
>
|
||||
<option>Todas as atividades</option>
|
||||
</select></label
|
||||
>
|
||||
<label class="text-sm font-bold"
|
||||
>Pessoa pública<select
|
||||
class="mt-1.5 h-11 w-full rounded-[10px] border border-black/20 bg-white px-3 font-normal"
|
||||
>
|
||||
<option>Qualquer pessoa</option>
|
||||
</select></label
|
||||
>
|
||||
</div>
|
||||
<button class="primary mt-6 w-full">Ver fotos</button>
|
||||
<button class="mt-3 w-full text-sm font-extrabold text-[#184e92]">
|
||||
Limpar filtros
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 03 -->
|
||||
<section aria-labelledby="scene-3">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 03</p>
|
||||
<h2 id="scene-3" class="display mt-1 text-xl font-black">
|
||||
Foto aberta em contexto · detalhe sem mecânicas sociais
|
||||
</h2>
|
||||
<div class="stage mt-4 overflow-x-auto rounded-2xl border border-border bg-[#171412]">
|
||||
<div class="min-w-[1080px] p-8 text-white">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<button
|
||||
class="inline-flex h-11 items-center gap-2 rounded-lg border border-white/25 px-4 text-sm font-bold"
|
||||
>
|
||||
<svg class="icon"><use href="#arrow" /></svg>Voltar ao álbum</button
|
||||
><button
|
||||
aria-label="Fechar"
|
||||
class="grid size-11 place-items-center rounded-full bg-white/10"
|
||||
>
|
||||
<svg class="icon"><use href="#x" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="grid grid-cols-[minmax(0,1fr)_340px] overflow-hidden rounded-2xl bg-[#211d1b]"
|
||||
>
|
||||
<div class="photo-placeholder photo-d min-h-[650px] rounded-none"></div>
|
||||
<aside class="flex flex-col p-7">
|
||||
<p class="text-xs font-black tracking-[.1em] text-[#ffeb00] uppercase">
|
||||
Registro aprovado
|
||||
</p>
|
||||
<h3 class="display mt-3 text-2xl leading-tight font-black">
|
||||
Plenária pela saúde pública
|
||||
</h3>
|
||||
<dl class="mt-6 grid gap-5 text-sm">
|
||||
<div>
|
||||
<dt class="font-bold text-white/55">Data</dt>
|
||||
<dd class="mt-1">12 de setembro de 2026</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-bold text-white/55">Local</dt>
|
||||
<dd class="mt-1">Município selecionado · Bahia</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-bold text-white/55">Atividade</dt>
|
||||
<dd class="mt-1">Plenária</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-bold text-white/55">Quem aparece</dt>
|
||||
<dd class="mt-1">Jorge Solla e pessoa pública convidada</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<button class="primary mt-7 w-full">
|
||||
<svg class="icon"><use href="#download" /></svg>Baixar foto
|
||||
</button>
|
||||
<div class="mt-auto border-t border-white/15 pt-5">
|
||||
<p class="m-0 text-sm font-bold">É você nesta foto?</p>
|
||||
<a
|
||||
class="mt-1 inline-block text-sm text-white/70 underline underline-offset-4"
|
||||
href="#"
|
||||
>Peça a remoção desta imagem</a
|
||||
>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 04 -->
|
||||
<section aria-labelledby="scene-4">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 04</p>
|
||||
<h2 id="scene-4" class="display mt-1 text-xl font-black">
|
||||
Vazio honesto · nada parecido ocupa a grade
|
||||
</h2>
|
||||
<div class="stage mt-4 max-w-[900px] rounded-2xl border border-border bg-white p-8 sm:p-12">
|
||||
<div class="rounded-2xl border border-[#184e92]/15 bg-[#f9faff] px-6 py-14 text-center">
|
||||
<div
|
||||
class="mx-auto grid size-12 place-items-center rounded-full border border-[#184e92]/20 bg-white text-[#184e92]"
|
||||
>
|
||||
<svg class="icon size-6"><use href="#search" /></svg>
|
||||
</div>
|
||||
<h3 class="display mt-4 text-2xl font-black">Nada encontrado com esses filtros</h3>
|
||||
<p class="mx-auto mt-2 max-w-md text-sm leading-6 text-muted-foreground">
|
||||
Tente outro termo ou volte a ver todas as fotos aprovadas do álbum.
|
||||
</p>
|
||||
<div class="mt-4 flex flex-wrap justify-center gap-2 text-xs">
|
||||
<span class="rounded-full border border-border bg-white px-3 py-1.5"
|
||||
>Busca: “termo informado”</span
|
||||
><span class="rounded-full border border-border bg-white px-3 py-1.5"
|
||||
>Município selecionado</span
|
||||
><span class="rounded-full border border-border bg-white px-3 py-1.5"
|
||||
>Atividade selecionada</span
|
||||
>
|
||||
</div>
|
||||
<button class="primary mt-6 min-w-40">Limpar filtros</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 05 -->
|
||||
<section aria-labelledby="scene-5">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 05</p>
|
||||
<h2 id="scene-5" class="display mt-1 text-xl font-black">
|
||||
Canal de remoção discreto · rodapé do álbum
|
||||
</h2>
|
||||
<div class="stage mt-4 overflow-hidden rounded-2xl border border-border bg-white">
|
||||
<div class="mx-auto flex max-w-6xl items-center justify-between gap-6 px-8 py-7">
|
||||
<div>
|
||||
<p class="display text-base font-black">Aparece em alguma foto?</p>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Se você não quiser que ela fique pública, fale com a equipe responsável pelo acervo.
|
||||
</p>
|
||||
</div>
|
||||
<a href="#" class="secondary shrink-0">Pedir remoção de uma foto</a>
|
||||
</div>
|
||||
<footer class="bg-[#180a09] px-8 py-8 text-sm text-white/70">
|
||||
<div class="mx-auto flex max-w-6xl justify-between">
|
||||
<div>
|
||||
<b class="display text-white">Jorge Solla 1313</b>
|
||||
<p class="mt-1">Deputado Federal · PT · Bahia</p>
|
||||
</div>
|
||||
<div class="flex gap-6">
|
||||
<span>Fotos</span><span>Privacidade</span><span>Contato</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
111
docs/plans/album-fotos-publico-busca.md
Normal file
111
docs/plans/album-fotos-publico-busca.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# C233 — Álbum público de fotos com busca por data, local, atividade e pessoa pública
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-26
|
||||
Issue: #1368
|
||||
Priority: P2
|
||||
Impeccable: C — fluxo/rota pública nova (`/fotos`)
|
||||
Design UI: docs/plans/album-fotos-publico-busca-ui-design.html
|
||||
Appetite: ~1–2 dias eng; um outcome verificável — o cidadão chega à foto certa por data, município, atividade, pessoa pública ou termo, e só vê o que a curadoria aprovou
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O acervo de fotos do mandato está sendo montado e catalogado (C231/C232), mas não tem porta pública: quem viu Solla num comício, numa plenária, numa obra ou numa reunião — ou quem procura o registro de uma agenda — não tem onde navegar. O pedido do dono é transformar o acervo num álbum público do mandato: buscar por data, local (município), atividade (ex.: comício, plenária, reunião, obra) e por pessoa pública que aparece na foto — e chegar à foto que procura. Sem reconhecimento facial nesta fatia (C234); só entra o que a curadoria aprovar; e qualquer pessoa pode pedir a remoção de foto em que aparece.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** cidadão/eleitor no celular (apoiador, morador do município, alguém que esteve no evento), em estado de procura leve — quer ver/relembrar a foto, não estudar o acervo.
|
||||
- **Job principal:** achar a foto certa por data, município, atividade ou pessoa que aparece — e, chegando nela, entender o contexto real (quando, onde, o quê, quem).
|
||||
- **Fluxo desejado:** entra no álbum → navega por capítulos/álbuns (evento, data, município) ou já busca/filtra por data, município, atividade e pessoa pública (ou digita um termo) → vê a grade com legenda real (data · local · atividade · quem aparece) → abre a foto em contexto → nada casa = estado vazio explicando e oferecendo limpar filtros → se aparece numa foto, o canal "é você nesta foto? peça a remoção" está visível.
|
||||
- **Anti-goals de produto:** não é rede social (sem like/comentário/share com PII); não é dashboard; não expõe foto não aprovada; não cria segundo cadastro de pessoa (`Contact`); não é a busca facial (C234).
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[site] → /fotos
|
||||
→ capítulos/álbuns (evento · data · município) e/ou facetas: data, município, atividade, pessoa pública + termo
|
||||
→ grade de fotos aprovadas (legenda real: data · local · atividade · quem aparece)
|
||||
→ abre a foto em contexto → chega à foto que procura
|
||||
→ nada casa = vazio honesto (explica + limpar filtros)
|
||||
→ "é você nesta foto? peça a remoção" → atendimento humano → foto sai do público e do índice
|
||||
[outcome: a foto certa alcançada em poucos toques, só com o que foi aprovado]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/album-fotos-publico-busca-ui-design.html`
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- O visitante acha foto por data, município, atividade e pessoa pública (facetas single-value combináveis) e por termo — e chega à foto.
|
||||
- Só fotos aprovadas aparecem; foto não aprovada não é acessível nem por URL direta (fail-closed).
|
||||
- Pessoa pública só pelo catálogo curado, por nome canônico — nunca `Contact`, nunca nome inferido por rosto.
|
||||
- Foto mostra contexto real (data, local, atividade, quem aparece); nada inventado.
|
||||
- Vazio honesto: sem resultado = estado que explica e sugere limpar filtros; nunca preencher com "parecidas".
|
||||
- Remoção a pedido: canal visível; após atendimento humano registrado a foto some do público e do índice de busca; sem esse registro, não republica (fail-closed, right to erasure).
|
||||
- Cache/revalidação por tag: edição de curadoria reflete no site; pull-down de foto/álbum é imediato.
|
||||
- Acessibilidade: grade com `alt` real (descrição curada), navegação por teclado, foco visível e contraste; mobile primeiro.
|
||||
- Performance: grade leve no celular (miniaturas, carregamento sob demanda); imagens same-origin pelo proxy de mídia do Teqo — bucket privado, sem hotlink.
|
||||
- Guardrail eleitoral: pull-down de foto/álbum não depende de deploy; abrir a rota é decisão do gate (ver Questões).
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — a superfície é busca/navegação de acervo, não analytics: nenhum número, contador ou ranking aparece para o visitante.
|
||||
- **Decisões desbloqueadas:** N/A — a decisão do visitante é qualitativa ("esta é a foto que eu procurava?"); nenhuma escolha numérica nomeável.
|
||||
- **Forma:** N/A — restrição de produto: proibido contador de vaidade ("mais vistas", "populares") ou métrica de engajamento na superfície; qualquer medição interna, se houver, é anônima e não aparece no site.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- Rota pública nova: **`/fotos`** (literal proposto; o gate confirma) — contrato de URL público, canônico e indexável; facetas single-value na URL no padrão de `contentPieceCatalog` (data, município, atividade, pessoa pública) + busca textual.
|
||||
- **Sem reconhecimento facial** nesta fatia; busca por **pessoa pública** só pelo catálogo curado (nome canônico; nunca `Contact`, nunca nome inferido por rosto).
|
||||
- **Só entra no público o que a curadoria aprovar** (estado de publicação explícito) — acervo bruto nunca vaza.
|
||||
- **Remoção a pedido:** canal visível "é você nesta foto? peça a remoção" com atendimento humano; foto removida some do público e do índice de busca (right to erasure) — fail-closed: sem atendimento registrado, não republica.
|
||||
- Imagens servidas same-origin pelo proxy de mídia do Teqo (bucket permanece privado); nada de hotlink para Flickr.
|
||||
- Vazio honesto: sem resultado = estado explicando e sugerindo limpar filtros; nunca preencher com "parecidas".
|
||||
- Cache público com revalidação por tag (edição de curadoria reflete no site); pull-down de foto/álbum precisa ser imediato.
|
||||
- **Janela eleitoral (eleição 04/10/2026):** registrar a questão de timing — publicar a rota antes ou depois da eleição / atrás de gate de curadoria com pull-down imediato. Recomendação do plano: **não abrir superfície pública nova nos dias finais de campanha; tratar C233/C234 como pós-eleição**, mantendo C231/C232 internas. É pergunta do gate, não bloqueio do plano.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** rota nova em `src/app/(frontend)/` (`/fotos`) com componentes públicos próprios; leitura pública cacheada no padrão de `src/utilities/content/contentPieceReads.ts`; contrato puro de URL/facetas/view model no padrão de `src/lib/contentPieceCatalog.ts`; mídia pelo proxy existente das portas de mídia privada; tag de cache registrada no dono da allowlist de revalidação (`src/utilities/revalidateRequest.ts`).
|
||||
- **Precedente a olhar:** `/conteudos` (`page.tsx`, `loading.tsx`, estados `ContentPieceEmptyState`/`ContentPieceNoResults`); facetas/URL internas `src/utilities/speech/speechListUrl.ts` e `src/lib/speechFacets.ts`; design de facetas `docs/plans/acervo-paridade-busca-filtros-ui-design.html`; visibilidade eleitoral fail-closed (`hidden`/`isPostVisible`, `AGENTS-public.md`).
|
||||
- **Risco de acoplamento:** contrato de URL público é congelado por AGENTS — canônico/indexável e facetas single-value no vocabulário existente; tag nova de revalidação exige editar a allowlist fechada; imagem pública passa pelo proxy same-origin e pela allowlist do otimizador do Next; não tocar em `Contact`; módulo utilitário novo de topo quebra o pin de convenções (`tests/unit/codebaseConventions.unit.spec.ts:421-427`); pull-down eleitoral não pode depender de deploy.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Duras:** C232 (catálogo com atividade/local/pessoas — sem esses campos não há facetas) e C231 (acervo — sem acervo curado não há foto para publicar). Nenhuma das duas entra aqui.
|
||||
- **Soft:** C234 (reconhecimento facial) é o sucessor declarado, fora desta fatia.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Reconhecimento facial (C234); upload público; edição de imagem.
|
||||
- Analytics de vaidade / contador de visualizações.
|
||||
- App/mobile nativo; reescrita da home.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Álbuns por evento mantidos à mão.** Se alguém "só completar": curadoria dupla (álbum + foto) e álbuns vazios por falta de gesto. **Corte neste item:** capítulos/álbuns derivados do catálogo curado, sem segunda curadoria paralela.
|
||||
- **Lightbox social (like/comentário/compartilhar com PII).** Se alguém "só completar": o álbum vira rede social e superfície de moderação. **Corte neste item:** sem reações nem comentário; quem aparece é dado de legenda/filtro, não perfil.
|
||||
- **Busca por conteúdo dentro da foto ("acha a faixa, a placa").** Se alguém "só completar": visão computacional entra aqui — já é C232. **Corte neste item:** esta busca consome os campos que C232 produz; aqui não se infere nada.
|
||||
- **Sitemap de 6,5k páginas de foto.** Se alguém "só completar": cada foto vira página indexável e o SEO explode. **Corte neste item:** canônico/indexável é a rota do álbum com facetas; foto avulsa não ganha página própria nesta fatia.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Quando abrir a superfície?** **Opções:** A) pós-eleição (04/10), com C231/C232 internas | B) antes, atrás de gate de curadoria com pull-down imediato | C) publicar agora sem ressalva. **Recomendação:** A — não abrir superfície pública nova nos dias finais de campanha; tratar C233/C234 como pós-eleição. _(assumido — validar com produto)_
|
||||
- **Qual URL?** **Opções:** A) `/fotos` (proposto) | B) `/galeria` | C) `/acervo`. **Recomendação:** A — curto, em pt-BR e descreve o conteúdo; `/acervo` colide com o acervo interno de falas e `/galeria` é genérica; o gate confirma. _(assumido — validar com produto)_
|
||||
- **Foto avulsa ou álbuns/capítulos por evento?** **Opções:** A) os dois níveis — navegação por álbum e busca que chega à foto | B) só foto avulsa | C) só álbuns. **Recomendação:** A — a foto é a unidade do resultado, mas capítulos dão contexto de evento e ajudam quem não sabe o que procura. _(assumido — validar com produto)_
|
||||
- **Pessoa pública aparece com nome ou como filtro "aparições"?** **Opções:** A) nome canônico visível na legenda e como filtro | B) só filtro "pessoas que aparecem", sem nomear | C) sem faceta de pessoa. **Recomendação:** A — é a busca pedida pelo dono e o nome vem do catálogo curado; o canal de remoção é o contrapeso. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue [#1368](https://github.com/fsolla/teqo/issues/1368)
|
||||
- Design UI (gate): `docs/plans/album-fotos-publico-busca-ui-design.html`.
|
||||
- Dependências: C231 (acervo) e C232 (catálogo com atividade/local/pessoas).
|
||||
- Precedentes: `docs/plans/acervo-busca-por-sentido.md`, `docs/plans/central-conteudos-importar-perfil.md`, `docs/plans/acervo-paridade-busca-filtros-ui-design.html`.
|
||||
- Arquivos-pista: `src/lib/contentPieceCatalog.ts` · `src/utilities/content/contentPieceReads.ts` · `src/app/(frontend)/conteudos/(catalog)/page.tsx` · `src/app/(frontend)/conteudos/[slug]/midia/route.ts` · `src/utilities/revalidateRequest.ts` · `src/utilities/speech/speechListUrl.ts` · `src/lib/speechFacets.ts`.
|
||||
- `AGENTS.md` (contrato de URL público, mídia same-origin) · `AGENTS-public.md` (visibilidade eleitoral fail-closed).
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
5/5 — (1) fatia = um outcome verificável (achar a foto por data, município, atividade, pessoa ou termo, só com aprovadas); (2) appetite ~1–2 dias comporta a rota pública sobre o catálogo de C232, com facetas, busca, vazio, remoção e cache, sem construir o acervo; (3) persona, job e aceite verificáveis, incluindo acessibilidade; (4) direção no codebase é hipótese com precedentes nomeados; (5) zero decisão dura de engenharia (a rota literal é decisão de produto sujeita ao gate; schema, migration, cache e modelagem ficam no plano de implementação).
|
||||
680
docs/plans/busca-fotos-por-selfie-ui-design.html
Normal file
680
docs/plans/busca-fotos-por-selfie-ui-design.html
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
<!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 — C234 · Busca de fotos por selfie</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, Arimo, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-card: var(--card);
|
||||
--color-ring: var(--ring);
|
||||
--color-destructive: var(--destructive);
|
||||
}
|
||||
[data-theme='campaign-site'] {
|
||||
--background: #fff8f2;
|
||||
--foreground: #201b1a;
|
||||
--card: #ffffff;
|
||||
--primary: #e4102f;
|
||||
--primary-foreground: #ffffff;
|
||||
--muted: #efe9e7;
|
||||
--muted-foreground: #6c615e;
|
||||
--border: rgb(32 27 26 / 13%);
|
||||
--ring: #e4102f;
|
||||
--destructive: #b42318;
|
||||
--brand-red: #e4102f;
|
||||
--brand-blue: #184e92;
|
||||
--brand-yellow: #ffeb00;
|
||||
--site-red: #ae1603;
|
||||
--cream: #fff8f2;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, Arimo, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.display {
|
||||
font-family: 'Exo 2', Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.stage {
|
||||
box-shadow: 0 22px 70px rgb(32 27 26 / 14%);
|
||||
}
|
||||
.primary {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--brand-red);
|
||||
padding: 10px 16px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 8px 18px rgb(228 16 47 / 18%);
|
||||
}
|
||||
.secondary {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
padding: 10px 16px;
|
||||
color: #201b1a;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
.photo-placeholder {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(145deg, transparent 48%, rgb(255 255 255 / 38%) 49%, transparent 51%),
|
||||
linear-gradient(135deg, #d6d3d1, #78716c);
|
||||
}
|
||||
.photo-placeholder::before {
|
||||
content: 'NEEDS ASSET · foto aprovada';
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 9px;
|
||||
z-index: 1;
|
||||
border-radius: 999px;
|
||||
background: rgb(255 255 255 / 90%);
|
||||
padding: 4px 7px;
|
||||
color: #57534e;
|
||||
font-size: 8px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.photo-placeholder::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12%;
|
||||
bottom: -15%;
|
||||
width: 44%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 999px 999px 30% 30%;
|
||||
background: rgb(87 83 78 / 38%);
|
||||
box-shadow: -70px 18px 0 -20px rgb(120 113 108 / 42%);
|
||||
}
|
||||
.selfie-preview {
|
||||
background:
|
||||
radial-gradient(circle at 50% 38%, #d6d3d1 0 13%, transparent 13.5%),
|
||||
radial-gradient(ellipse at 50% 74%, #a8a29e 0 25%, transparent 25.5%),
|
||||
linear-gradient(145deg, #f5f5f4, #d6d3d1);
|
||||
}
|
||||
.focus-ring:focus-visible {
|
||||
outline: 3px solid var(--brand-yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.spinner {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border: 4px solid rgb(24 78 146 / 16%);
|
||||
border-top-color: #184e92;
|
||||
border-radius: 999px;
|
||||
animation: spin 900ms linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.spinner {
|
||||
animation: none;
|
||||
border-top-color: rgb(24 78 146 / 16%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-[#efeeec] text-foreground antialiased">
|
||||
<svg class="absolute size-0 overflow-hidden" aria-hidden="true">
|
||||
<symbol id="camera" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M14.5 4 16 7h3a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h3l1.5-3h5Z"
|
||||
/>
|
||||
<circle cx="12" cy="13" r="4" />
|
||||
</symbol>
|
||||
<symbol id="shield" viewBox="0 0 24 24">
|
||||
<path d="M20 13c0 5-3.5 7.5-8 9-4.5-1.5-8-4-8-9V5l8-3 8 3v8Z" />
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
</symbol>
|
||||
<symbol id="device" viewBox="0 0 24 24">
|
||||
<rect x="5" y="2" width="14" height="20" rx="2" />
|
||||
<path d="M9 18h6" />
|
||||
</symbol>
|
||||
<symbol id="spark" viewBox="0 0 24 24">
|
||||
<path d="m12 3-1.7 4.6L6 9.3l4.3 1.7 1.7 4.7 1.7-4.7L18 9.3l-4.3-1.7L12 3Z" />
|
||||
<path d="m19 14-.9 2.1L16 17l2.1.9L19 20l.9-2.1L22 17l-2.1-.9L19 14Z" />
|
||||
</symbol>
|
||||
<symbol id="arrow" viewBox="0 0 24 24"><path d="m15 18-6-6 6-6" /></symbol>
|
||||
<symbol id="chev" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6" /></symbol>
|
||||
<symbol id="x" viewBox="0 0 24 24"><path d="m18 6-12 12M6 6l12 12" /></symbol>
|
||||
<symbol id="check" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6" /></symbol>
|
||||
<symbol id="upload" viewBox="0 0 24 24"><path d="M12 16V4M7 9l5-5 5 5M5 20h14" /></symbol>
|
||||
<symbol id="image" viewBox="0 0 24 24">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
<circle cx="9" cy="9" r="2" />
|
||||
<path d="m21 15-5-5L5 21" />
|
||||
</symbol>
|
||||
<symbol id="alert" viewBox="0 0 24 24">
|
||||
<path d="M12 3 2 21h20L12 3Z" />
|
||||
<path d="M12 9v5M12 18h.01" />
|
||||
</symbol>
|
||||
<symbol id="trash" viewBox="0 0 24 24">
|
||||
<path d="M3 6h18M8 6V4h8v2M19 6l-1 15H6L5 6M10 11v5M14 11v5" />
|
||||
</symbol>
|
||||
<symbol id="lock" viewBox="0 0 24 24">
|
||||
<rect x="3" y="11" width="18" height="10" rx="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<main class="mx-auto flex max-w-[1480px] flex-col gap-16 px-4 py-10 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="rounded-full bg-[#e4102f] px-3 py-1 text-xs font-extrabold text-white"
|
||||
>C234 · gate hi-fi</span
|
||||
><span class="rounded-full border border-border bg-white px-3 py-1 text-xs font-bold"
|
||||
>Fluxo sensível · LGPD fail-closed</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="display mt-4 text-3xl font-black tracking-[-.03em]">
|
||||
Encontre você nas fotos — privado, explícito e sem placar de semelhança
|
||||
</h1>
|
||||
<p class="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||
A pessoa parte do álbum, entende o uso de biometria, escolhe a própria selfie e recebe
|
||||
apenas fotos públicas aprovadas em que aparece. A selfie permanece no dispositivo.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- CENA 01 -->
|
||||
<section aria-labelledby="s1">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 01</p>
|
||||
<h2 id="s1" class="display mt-1 text-xl font-black">
|
||||
Desktop ~1280 · entrada no contexto da /fotos
|
||||
</h2>
|
||||
<div class="stage mt-4 overflow-x-auto rounded-2xl border border-border bg-white">
|
||||
<div class="min-h-[660px] min-w-[1180px] bg-white">
|
||||
<header class="bg-[#ae1603]">
|
||||
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-8">
|
||||
<div class="relative h-[52px] w-[144px] overflow-hidden">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
alt="Jorge Solla"
|
||||
class="absolute top-[-29.5px] left-[-22.4px] h-auto w-[189px] max-w-none"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-white">Fotos</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mx-auto max-w-6xl px-8 py-9">
|
||||
<nav class="text-sm text-muted-foreground">
|
||||
Início <span class="px-1">/</span> Fotos
|
||||
</nav>
|
||||
<div class="mt-6 grid grid-cols-[1fr_420px] items-stretch gap-6">
|
||||
<div class="rounded-2xl bg-[#fff8f2] p-8">
|
||||
<p class="text-xs font-black tracking-[.1em] text-[#e4102f] uppercase">
|
||||
Álbum público
|
||||
</p>
|
||||
<h3 class="display mt-3 text-[38px] leading-[1.02] font-black tracking-[-.03em]">
|
||||
Procure uma foto por data, lugar ou atividade.
|
||||
</h3>
|
||||
<p class="mt-4 max-w-lg text-base leading-6 text-muted-foreground">
|
||||
Use os filtros para navegar pelos registros aprovados da caminhada.
|
||||
</p>
|
||||
<div
|
||||
class="mt-7 flex h-12 items-center gap-3 rounded-[10px] border border-black/20 bg-white px-4 text-sm text-muted-foreground"
|
||||
>
|
||||
<svg class="icon"><use href="#image" /></svg>Buscar no álbum…
|
||||
</div>
|
||||
</div>
|
||||
<aside
|
||||
class="relative overflow-hidden rounded-2xl bg-[linear-gradient(145deg,#184e92,#0d315f)] p-7 text-white"
|
||||
>
|
||||
<div
|
||||
class="absolute -right-12 -bottom-12 size-56 rounded-full border-[30px] border-[#ffeb00]/15"
|
||||
></div>
|
||||
<div
|
||||
class="relative grid size-12 place-items-center rounded-xl bg-white/12 text-[#ffeb00]"
|
||||
>
|
||||
<svg class="icon size-6"><use href="#spark" /></svg>
|
||||
</div>
|
||||
<h3 class="display relative mt-5 text-2xl font-black">Encontre você nas fotos</h3>
|
||||
<p class="relative mt-2 text-sm leading-6 text-white/80">
|
||||
Use uma selfie sua. Ela não sai deste dispositivo e não vira cadastro.
|
||||
</p>
|
||||
<button class="primary relative mt-7 w-full">
|
||||
Começar busca por selfie <svg class="icon"><use href="#chev" /></svg>
|
||||
</button>
|
||||
<p class="relative mt-3 text-center text-xs text-white/65">
|
||||
Sem conta · você controla a remoção
|
||||
</p>
|
||||
</aside>
|
||||
</div>
|
||||
<div class="mt-7 grid grid-cols-4 gap-4">
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 02 -->
|
||||
<section aria-labelledby="s2">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 02</p>
|
||||
<h2 id="s2" class="display mt-1 text-xl font-black">
|
||||
Desktop ~1280 · aviso de biometria e consentimento explícito
|
||||
</h2>
|
||||
<div
|
||||
class="stage relative mt-4 min-h-[720px] overflow-hidden rounded-2xl border border-border bg-[linear-gradient(rgb(24_10_9/55%),rgb(24_10_9/55%)),#fff8f2] p-8"
|
||||
>
|
||||
<div
|
||||
class="mx-auto max-w-[570px] rounded-2xl bg-white p-7 shadow-[0_28px_90px_rgb(0_0_0/26%)]"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="grid size-12 place-items-center rounded-xl bg-[#eef4fb] text-[#184e92]">
|
||||
<svg class="icon size-6"><use href="#shield" /></svg>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Fechar"
|
||||
class="grid size-10 place-items-center rounded-full bg-muted"
|
||||
>
|
||||
<svg class="icon"><use href="#x" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-5 text-xs font-black tracking-[.1em] text-[#184e92] uppercase">
|
||||
Antes da sua selfie
|
||||
</p>
|
||||
<h3 class="display mt-2 text-2xl font-black">Sua face é um dado biométrico sensível</h3>
|
||||
<p class="mt-3 text-sm leading-6 text-muted-foreground">
|
||||
Com sua autorização, comparamos a sua selfie com o índice permitido do álbum para
|
||||
mostrar somente fotos públicas aprovadas em que você aparece.
|
||||
</p>
|
||||
<div class="mt-5 grid gap-3 rounded-xl bg-[#f9faff] p-4 text-sm">
|
||||
<div class="flex gap-3">
|
||||
<svg class="icon mt-0.5 shrink-0 text-[#184e92]"><use href="#device" /></svg>
|
||||
<p>
|
||||
<b>A selfie não sai do dispositivo.</b><br /><span class="text-muted-foreground"
|
||||
>A imagem é processada aqui e não é guardada na galeria do site.</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<svg class="icon mt-0.5 shrink-0 text-[#184e92]"><use href="#lock" /></svg>
|
||||
<p>
|
||||
<b>Você pode desistir e pedir remoção.</b><br /><span
|
||||
class="text-muted-foreground"
|
||||
>O resultado não mostra nomes de terceiros nem percentual de semelhança.</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<label
|
||||
class="mt-5 flex cursor-pointer items-start gap-3 rounded-xl border border-black/15 p-4 text-sm leading-5"
|
||||
><span
|
||||
class="mt-0.5 grid size-5 shrink-0 place-items-center rounded border-2 border-[#184e92] bg-[#184e92] text-white"
|
||||
><svg class="icon size-3.5"><use href="#check" /></svg></span
|
||||
><span
|
||||
>Li e autorizo o uso temporário da minha biometria facial para procurar a mim mesmo
|
||||
neste álbum.</span
|
||||
></label
|
||||
>
|
||||
<p class="mt-3 text-xs leading-5 text-muted-foreground">
|
||||
Saiba como tratamos seus dados na
|
||||
<a href="#" class="font-bold text-[#184e92] underline underline-offset-2"
|
||||
>Política de Privacidade</a
|
||||
>.
|
||||
</p>
|
||||
<button class="primary mt-6 w-full">Concordar e escolher minha selfie</button>
|
||||
<button class="mt-3 w-full text-sm font-bold text-muted-foreground">Agora não</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 03 -->
|
||||
<section aria-labelledby="s3">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 03</p>
|
||||
<h2 id="s3" class="display mt-1 text-xl font-black">
|
||||
Mobile ~390 · captura ou envio, com processamento local evidente
|
||||
</h2>
|
||||
<div
|
||||
class="stage mt-4 w-[390px] overflow-hidden rounded-[30px] border border-border bg-white"
|
||||
>
|
||||
<header class="flex h-14 items-center justify-between bg-[#ae1603] px-4 text-white">
|
||||
<button aria-label="Voltar">
|
||||
<svg class="icon"><use href="#arrow" /></svg></button
|
||||
><span class="text-sm font-bold">Encontre você</span><span class="w-[18px]"></span>
|
||||
</header>
|
||||
<div class="px-5 py-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="h-1.5 flex-1 rounded-full bg-[#184e92]"></span
|
||||
><span class="h-1.5 flex-1 rounded-full bg-[#184e92]"></span
|
||||
><span class="h-1.5 flex-1 rounded-full bg-muted"></span>
|
||||
</div>
|
||||
<p class="mt-5 text-xs font-black tracking-[.08em] text-[#184e92] uppercase">
|
||||
Passo 2 de 3
|
||||
</p>
|
||||
<h3 class="display mt-1 text-2xl font-black">Escolha uma selfie nítida</h3>
|
||||
<p class="mt-2 text-sm leading-5 text-muted-foreground">
|
||||
Olhe para a câmera, com o rosto bem iluminado e sem outras pessoas na imagem.
|
||||
</p>
|
||||
<div
|
||||
class="selfie-preview relative mt-5 aspect-[4/5] overflow-hidden rounded-2xl border border-black/10"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-[15%_12%] rounded-[48%] border-2 border-dashed border-white/85"
|
||||
></div>
|
||||
<span
|
||||
class="absolute right-3 bottom-3 rounded-full bg-black/55 px-3 py-1.5 text-[10px] font-bold text-white"
|
||||
>Prévia no aparelho</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="mt-4 flex items-start gap-3 rounded-xl bg-[#eef4fb] p-3 text-sm text-[#143c70]"
|
||||
>
|
||||
<svg class="icon mt-0.5 shrink-0"><use href="#device" /></svg>
|
||||
<p class="leading-5">
|
||||
<b>Esta foto não sai do seu dispositivo.</b><br />O processamento acontece aqui.
|
||||
</p>
|
||||
</div>
|
||||
<button class="primary mt-5 w-full">
|
||||
<svg class="icon"><use href="#camera" /></svg>Usar esta selfie
|
||||
</button>
|
||||
<button class="secondary mt-2 w-full">
|
||||
<svg class="icon"><use href="#upload" /></svg>Escolher outra foto
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 04 -->
|
||||
<section aria-labelledby="s4">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 04</p>
|
||||
<h2 id="s4" class="display mt-1 text-xl font-black">
|
||||
Mobile ~390 · processando sem promessas ou percentuais
|
||||
</h2>
|
||||
<div
|
||||
class="stage mt-4 w-[390px] overflow-hidden rounded-[30px] border border-border bg-white"
|
||||
>
|
||||
<header
|
||||
class="flex h-14 items-center justify-center bg-[#ae1603] px-4 text-sm font-bold text-white"
|
||||
>
|
||||
Encontre você
|
||||
</header>
|
||||
<div class="flex min-h-[560px] flex-col items-center justify-center px-7 text-center">
|
||||
<div class="relative">
|
||||
<div
|
||||
class="selfie-preview size-28 rounded-full border-4 border-white shadow-[0_10px_28px_rgb(0_0_0/16%)]"
|
||||
></div>
|
||||
<div
|
||||
class="absolute -right-2 -bottom-1 grid size-10 place-items-center rounded-full bg-[#eef4fb]"
|
||||
>
|
||||
<div class="spinner !size-7 !border-[3px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="display mt-7 text-2xl font-black">Procurando você nas fotos aprovadas…</h3>
|
||||
<p class="mt-3 max-w-[290px] text-sm leading-6 text-muted-foreground">
|
||||
Mantenha esta tela aberta. Sua selfie continua somente neste aparelho.
|
||||
</p>
|
||||
<div class="mt-6 flex items-center gap-2 text-xs font-bold text-[#184e92]">
|
||||
<svg class="icon size-4"><use href="#shield" /></svg>Sem upload da imagem · sem
|
||||
cadastro
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 05 -->
|
||||
<section aria-labelledby="s5">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 05</p>
|
||||
<h2 id="s5" class="display mt-1 text-xl font-black">
|
||||
Desktop ~1280 · resultado mínimo: só fotos aprovadas em que você aparece
|
||||
</h2>
|
||||
<div class="stage mt-4 overflow-x-auto rounded-2xl border border-border bg-white">
|
||||
<div class="min-h-[780px] min-w-[1180px] bg-white">
|
||||
<header class="bg-[#ae1603]">
|
||||
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-8">
|
||||
<div class="relative h-[52px] w-[144px] overflow-hidden">
|
||||
<img
|
||||
src="../../public/campaign-kit/jorge-solla-negativo.png"
|
||||
alt="Jorge Solla"
|
||||
class="absolute top-[-29.5px] left-[-22.4px] h-auto w-[189px] max-w-none"
|
||||
/>
|
||||
</div>
|
||||
<button class="text-sm font-bold text-white">Voltar ao álbum</button>
|
||||
</div>
|
||||
</header>
|
||||
<main class="mx-auto max-w-6xl px-8 py-9">
|
||||
<div class="flex items-start justify-between gap-8 border-b border-border pb-7">
|
||||
<div>
|
||||
<p class="text-xs font-black tracking-[.1em] text-[#184e92] uppercase">
|
||||
Sua busca por selfie
|
||||
</p>
|
||||
<h3 class="display mt-2 text-[34px] font-black tracking-[-.03em]">
|
||||
Estas são as fotos em que encontramos você.
|
||||
</h3>
|
||||
<p class="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||
Mostramos apenas registros públicos aprovados. Não exibimos nomes de outras
|
||||
pessoas nem nota de semelhança.
|
||||
</p>
|
||||
</div>
|
||||
<button class="secondary shrink-0">Usar outra selfie</button>
|
||||
</div>
|
||||
<div class="mt-7 grid grid-cols-3 gap-x-4 gap-y-7">
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<p class="display mt-3 font-black">Plenária pela saúde pública</p>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
12 set 2026 · Município selecionado · Plenária
|
||||
</p>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<p class="display mt-3 font-black">Caminhada no centro</p>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
28 ago 2026 · Município selecionado · Caminhada
|
||||
</p>
|
||||
</button>
|
||||
</article>
|
||||
<article>
|
||||
<button class="focus-ring block w-full rounded-xl text-left">
|
||||
<div class="photo-placeholder aspect-[4/3] rounded-xl"></div>
|
||||
<p class="display mt-3 font-black">Encontro com a comunidade</p>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
16 ago 2026 · Município selecionado · Reunião
|
||||
</p>
|
||||
</button>
|
||||
</article>
|
||||
</div>
|
||||
<div class="mt-10 flex items-center justify-between rounded-xl bg-[#fff8f2] p-5">
|
||||
<div>
|
||||
<p class="font-extrabold">Quer controlar sua presença no álbum?</p>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Saia do índice ou peça a remoção de uma foto.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm font-extrabold text-[#184e92] underline underline-offset-4"
|
||||
>Gerenciar minha presença</a
|
||||
>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 06 -->
|
||||
<section aria-labelledby="s6">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 06</p>
|
||||
<h2 id="s6" class="display mt-1 text-xl font-black">
|
||||
Mobile ~390 · vazio honesto, sem “parecidos”
|
||||
</h2>
|
||||
<div
|
||||
class="stage mt-4 w-[390px] overflow-hidden rounded-[30px] border border-border bg-white"
|
||||
>
|
||||
<header class="flex h-14 items-center justify-between bg-[#ae1603] px-4 text-white">
|
||||
<button aria-label="Voltar">
|
||||
<svg class="icon"><use href="#arrow" /></svg></button
|
||||
><span class="text-sm font-bold">Resultado</span><span class="w-[18px]"></span>
|
||||
</header>
|
||||
<div class="flex min-h-[570px] flex-col items-center justify-center px-6 text-center">
|
||||
<div class="grid size-14 place-items-center rounded-full bg-[#eef4fb] text-[#184e92]">
|
||||
<svg class="icon size-7"><use href="#image" /></svg>
|
||||
</div>
|
||||
<h3 class="display mt-5 text-2xl font-black">Não encontramos você</h3>
|
||||
<p class="mt-2 max-w-[310px] text-sm leading-6 text-muted-foreground">
|
||||
Talvez a luz ou o ângulo tenham dificultado a busca — ou ainda não haja uma foto
|
||||
pública aprovada sua no álbum.
|
||||
</p>
|
||||
<p class="mt-3 text-sm font-bold">
|
||||
Não mostramos pessoas parecidas para preencher a tela.
|
||||
</p>
|
||||
<button class="primary mt-7 w-full">Tentar outra foto</button>
|
||||
<a href="#" class="mt-4 text-sm font-bold text-[#184e92] underline underline-offset-3"
|
||||
>Voltar ao álbum</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 07 -->
|
||||
<section aria-labelledby="s7">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 07</p>
|
||||
<h2 id="s7" class="display mt-1 text-xl font-black">
|
||||
Fail-closed · consentimento não configurado, fluxo indisponível
|
||||
</h2>
|
||||
<div class="stage mt-4 max-w-[920px] rounded-2xl border border-border bg-white p-8 sm:p-12">
|
||||
<div class="rounded-2xl border border-amber-300 bg-amber-50 px-6 py-10 text-center">
|
||||
<div
|
||||
class="mx-auto grid size-12 place-items-center rounded-full border border-amber-300 bg-white text-amber-800"
|
||||
>
|
||||
<svg class="icon size-6"><use href="#alert" /></svg>
|
||||
</div>
|
||||
<p class="mt-5 text-xs font-black tracking-[.1em] text-amber-800 uppercase">
|
||||
Proteção de dados
|
||||
</p>
|
||||
<h3 class="display mt-2 text-2xl font-black text-amber-950">
|
||||
A busca por selfie está indisponível
|
||||
</h3>
|
||||
<p class="mx-auto mt-3 max-w-lg text-sm leading-6 text-amber-900">
|
||||
O texto de consentimento necessário ainda não está configurado. Por segurança, não
|
||||
podemos receber nem processar sua selfie agora.
|
||||
</p>
|
||||
<p class="mx-auto mt-3 max-w-lg text-sm font-bold text-amber-950">
|
||||
Nenhuma imagem foi capturada ou enviada.
|
||||
</p>
|
||||
<a href="#" class="secondary mt-6 min-w-44 border-amber-300"
|
||||
>Voltar ao álbum de fotos</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CENA 08 -->
|
||||
<section aria-labelledby="s8">
|
||||
<p class="text-xs font-extrabold tracking-[.08em] text-[#e4102f] uppercase">Cena 08</p>
|
||||
<h2 id="s8" class="display mt-1 text-xl font-black">
|
||||
Mobile ~390 · opt-out e pedido de remoção com confirmação
|
||||
</h2>
|
||||
<div class="mt-4 flex flex-wrap items-start gap-8">
|
||||
<div class="stage w-[390px] overflow-hidden rounded-[30px] border border-border bg-white">
|
||||
<header class="flex h-14 items-center justify-between bg-[#ae1603] px-4 text-white">
|
||||
<button aria-label="Voltar">
|
||||
<svg class="icon"><use href="#arrow" /></svg></button
|
||||
><span class="text-sm font-bold">Minha presença</span><span class="w-[18px]"></span>
|
||||
</header>
|
||||
<div class="px-5 py-6">
|
||||
<p class="text-xs font-black tracking-[.08em] text-[#184e92] uppercase">
|
||||
Privacidade
|
||||
</p>
|
||||
<h3 class="display mt-1 text-2xl font-black">Você decide se quer aparecer</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Escolha o pedido. A equipe registra o atendimento e mantém a proteção fechada até
|
||||
concluir.
|
||||
</p>
|
||||
<label class="mt-5 flex gap-3 rounded-xl border-2 border-[#184e92] bg-[#f9faff] p-4"
|
||||
><span
|
||||
class="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full border-[5px] border-[#184e92]"
|
||||
></span
|
||||
><span
|
||||
><b class="text-sm">Sair do índice de busca</b
|
||||
><small class="mt-1 block text-xs leading-5 text-muted-foreground"
|
||||
>Seu rosto deixa de ser usado para localizar fotos por selfie.</small
|
||||
></span
|
||||
></label
|
||||
>
|
||||
<label class="mt-3 flex gap-3 rounded-xl border border-border p-4"
|
||||
><span class="mt-0.5 size-5 shrink-0 rounded-full border-2 border-stone-400"></span
|
||||
><span
|
||||
><b class="text-sm">Pedir remoção de uma foto</b
|
||||
><small class="mt-1 block text-xs leading-5 text-muted-foreground"
|
||||
>Informe qual imagem não deve continuar pública.</small
|
||||
></span
|
||||
></label
|
||||
>
|
||||
<button class="primary mt-6 w-full">Continuar</button
|
||||
><a href="#" class="mt-4 block text-center text-sm font-bold text-muted-foreground"
|
||||
>Cancelar</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="stage relative w-[390px] overflow-hidden rounded-[30px] border border-border bg-[linear-gradient(rgb(32_27_26/38%),rgb(32_27_26/38%)),white]"
|
||||
>
|
||||
<div class="h-[690px]"></div>
|
||||
<div
|
||||
class="absolute inset-x-4 top-1/2 -translate-y-1/2 rounded-2xl bg-white p-5 shadow-[0_24px_70px_rgb(0_0_0/28%)]"
|
||||
>
|
||||
<div class="grid size-11 place-items-center rounded-full bg-red-50 text-[#b42318]">
|
||||
<svg class="icon size-5"><use href="#trash" /></svg>
|
||||
</div>
|
||||
<h3 class="display mt-4 text-xl font-black">Confirmar saída do índice?</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Vamos registrar seu pedido. Até o atendimento ser concluído, sua presença permanece
|
||||
protegida e não deve voltar ao índice.
|
||||
</p>
|
||||
<button
|
||||
class="mt-6 flex min-h-11 w-full items-center justify-center rounded-[10px] bg-[#b42318] px-4 text-sm font-extrabold text-white"
|
||||
>
|
||||
Confirmar saída do índice
|
||||
</button>
|
||||
<button class="mt-3 w-full text-sm font-bold text-muted-foreground">
|
||||
Voltar sem alterar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
106
docs/plans/busca-fotos-por-selfie.md
Normal file
106
docs/plans/busca-fotos-por-selfie.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# C234 — Busca por selfie — visitante encontra as fotos em que aparece
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-26
|
||||
Issue: #1369
|
||||
Priority: P2
|
||||
Impeccable: C — fluxo público novo (busca por selfie na área pública de fotos)
|
||||
Design UI: docs/plans/busca-fotos-por-selfie-ui-design.html
|
||||
Appetite: ~2–3 dias eng + aval jurídico (produto); um outcome verificável — com consentimento ativo, o visitante encontra no acervo público aprovado as fotos em que aparece, sem que a selfie saia do dispositivo e sem score
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
Quem compareceu a um evento ou plenária com Jorge Solla quer se achar no acervo — hoje as fotos aprovadas vivem em `/fotos` (C233), mas encontrar-se no meio de centenas é garimpo manual. O pedido do dono é direto: a pessoa tira/envia uma foto própria e vê as fotos públicas em que aparece — com consentimento explícito, sem nomes de terceiros e sem score de semelhança. Reconhecimento facial é permitido e comum em eventos/festas; o desenho tem de ser fail-closed em consentimento (exigência travada do repo) e processar o mínimo possível. É dependente por natureza: sem o acervo catalogado e aprovado (C231/C232) e a superfície `/fotos` (C233), não há o que buscar. Como o tema é biometria de face — dado sensível —, o appetite embute aval jurídico e DPIA como dependência de produto, com a eleição de 04/10/2026 no calendário.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** visitante que foi a um evento com Solla, no celular, curioso para se ver no acervo, sem conta na campanha.
|
||||
- **Job principal:** encontrar, entre as fotos públicas aprovadas, as que eu apareço.
|
||||
- **Fluxo desejado:** abre `/fotos` → toca "Encontre você nas fotos" → lê o aviso (biometria/LGPD) e consente; sem consentimento configurado o fluxo nem abre → tira/envia uma selfie → o reconhecimento roda no dispositivo (a selfie não sobe) → vê só as fotos aprovadas em que aparece, sem nome de terceiros e sem score → decide se baixa/compartilha → a qualquer momento pede para sair do índice e/ou despublicar uma foto, e o pedido fica registrado.
|
||||
- **Anti-goals de produto:** não é vigilância nem CRM de rostos; não identifica terceiros; não cruza com `Contact`/leadership; não oferece busca por foto de outra pessoa; não abre sem consentimento; não vira feature social.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[fotos] → "Encontre você nas fotos" → aviso + consentimento explícito
|
||||
→ sem consentimento ativo: recusa clara (fail-closed), fim
|
||||
→ tira/envia selfie → reconhecimento no dispositivo (a selfie não sai daqui)
|
||||
→ resultado: só as fotos aprovadas em que a pessoa consultante aparece (sem terceiros, sem score)
|
||||
→ remoção: sair do índice / despublicar foto → atendimento registrado
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/busca-fotos-por-selfie-ui-design.html` — por que C: superfície pública nova (permissão/captura, estados de consentimento, resultado, vazio honesto, remoção), nas cenas 390/1280; o HTML hi-fi é o registro do aceite e a fonte de verdade do port.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Com consentimento ativo, o visitante tira/envia uma selfie e recebe somente as fotos do acervo público aprovado em que aparece.
|
||||
- Sem consentimento ativo (texto aprovado não configurado), o fluxo recusa com linguagem clara — nunca meio aberto, nem na UI nem no servidor.
|
||||
- Nenhum rastro da selfie no servidor: por padrão a imagem não sobe; se a implementação escolher upload consentido, retenção zero documentada após o match.
|
||||
- Remoção/opt-out funciona: sair do índice e despublicar fotos a pedido, com atendimento registrado; fail-closed até atender.
|
||||
- Nenhum score, percentual ou nome de terceiro em nenhum estado — nada de "outras pessoas parecidas".
|
||||
- Antiabuso: limite de tentativas anônimas, sem criação de conta obrigatória.
|
||||
- O resultado é o mesmo acervo aprovado para o público pela régua do C232/C233 — nenhuma foto privada/bruta abre porta lateral.
|
||||
- **Guardrails:** Consent/LGPD fail-closed (conceito travado); sem cruzamento com `Contact`/leadership; biometria não vira cadastro de pessoa.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — o resultado é "quais fotos", nunca "quão parecido". Score/percentual de semelhança ("match 87%") é anti-goal explícito do dono, não um dado a formatar.
|
||||
- **Decisões desbloqueadas:** N/A — a escolha do visitante ("essa foto é minha, quero guardar/compartilhar") é qualitativa; nenhum ator decide a partir de número.
|
||||
- **Forma:** N/A — restrição de produto: **sem score/percentual** de semelhança em nenhum estado da superfície; a única saída é a lista de fotos.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Consentimento fail-closed:** sem Consent ativo (resolvido por `key` estável, com texto versionado e hash) o fluxo não habilita — nem na UI, nem no servidor. Texto/chave exatos são da implementação, o conceito é travado.
|
||||
- **Escopo do índice facial — pergunta central do gate.** Opções: (A) **só consentidos/catálogo curado** — indexa rostos apenas de quem aderiu (equipe, lideranças, figuras públicas) e a busca por selfie só devolve se o rosto consultante já estiver indexado (mais restrito, mais seguro, UX pior); (B) **índice de todos os rostos do acervo público aprovado**, mas a resposta só se refere à própria pessoa consultante — sem nomes de terceiros, sem listar outras pessoas, com DPIA + aval jurídico e mecanismo de remoção do índice; (C) não indexar rostos anônimos e só habilitar busca para pessoas previamente cadastradas com consentimento. **Recomendação do plano:** A/C no MVP (falha fechada), evoluindo para B somente com aval jurídico documentado e DPIA — o gate decide.
|
||||
- **Selfie nunca sai do dispositivo por padrão:** detecção/embedding facial no cliente (precedente de visão on-device do repo); ao servidor vai só o vetor, sem imagem — se a opção de upload consentido for escolhida, retenção zero após o match e nunca armazenar em `media` pública.
|
||||
- **Resposta mínima:** mostra só as fotos aprovadas em que a pessoa consultante aparece; nunca "outras pessoas parecidas", nunca nome de terceiro, nunca score.
|
||||
- **Remoção:** pessoa pode pedir para sair do índice e ter fotos despublicadas; atendimento registrado; fail-closed até atender.
|
||||
- **Nada de foto privada/bruta** no resultado: só acervo aprovado para o público (mesma régua do C233).
|
||||
- **Antiabuso:** limite de tentativas anônimas (rate limit), sem criação de conta obrigatória.
|
||||
- **Janela eleitoral (eleição 04/10/2026) e DPIA:** superfície pública de biometria não abre sem revisão jurídica; registrar como dependência de produto e timing pós-eleição (o gate decide).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** superfície `/fotos` do C233 (rota + componentes), um fluxo client-side para captura/embedding (precedente `src/components/cards/`), `src/utilities/` para consentimento por key, rate limit e a rota de consulta; qualquer arquivo de selfie, se existir, atrás do gate privado padrão.
|
||||
- **Precedente a olhar:** `src/components/cards/cardCutout.ts` (visão on-device; a foto nunca sai do navegador), `src/utilities/campaignConsent.ts` + `src/lib/campaignConsentKeys.ts` (Consent por key fail-closed), `src/utilities/privateMedia/` + `src/app/(frontend)/conteudos/[slug]/midia/route.ts` (arquivo privado atrás de gate), `src/utilities/ai/rateLimit.ts` (antiabuso anônimo), `src/utilities/sameOriginRequest.ts` (POST público).
|
||||
- **Risco de acoplamento:** vocabulário colide — "biometria" no repo hoje é WebAuthn/passkeys (`src/utilities/campaignBiometricsPrompt.ts`), não face. O gate de consentimento e a régua de publicação (C232/C233) são donos; esta feature não cria segunda régua nem segundo cadastro de pessoa. Índice facial é infra própria (o C229 limita embeddings ao acervo interno de falas — implementação decide o como).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Duras: C231 (acervo de fotos), C232 (catálogo/aprovação), C233 (superfície pública `/fotos`).
|
||||
- Duras de produto: aval jurídico + DPIA antes de expor superfície pública de biometria; timing pós-eleição (04/10/2026).
|
||||
- Soft: C229 (`docs/plans/acervo-busca-por-sentido.md`) — precedente de índice próprio, abordagem fica na implementação.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Indexação biométrica de vídeos/gravações; moderação automática; API pública de reconhecimento; app nativo.
|
||||
- Nomeação/identificação de terceiros; galeria "quem é quem"; cruzamento com `Contact`/leadership; qualquer mecânica social (seguir/curtir/perfil público).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Só completar" com identificação de todo mundo.** Se alguém "só completar": nomear automaticamente cada rosto do acervo — virou vigilância/CRM de rostos. **Corte neste item:** só as fotos da pessoa consultante; nunca nome de terceiro, nunca diretório de rostos.
|
||||
- **Upload ilimitado de selfies / galeria de selfies.** Se alguém "só completar": guardar selfies para "melhorar o match". **Corte neste item:** por padrão a selfie não sobe; sem galeria, sem retenção de imagem.
|
||||
- **Face matching em tempo real.** Se alguém "só completar": busca ao vivo no evento, fila de câmeras. **Corte neste item:** busca assíncrona sob demanda dentro da `/fotos`.
|
||||
- **Shareability social.** Se alguém "só completar": ranking/feed de "fotos mais parecidas". **Corte neste item:** resultado privado da consulta, sem métrica e sem exposição.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Escopo do índice facial (pergunta central do gate)?** **Opções:** A) só consentidos/catálogo curado | B) todos os rostos do acervo público aprovado, com resposta só sobre a própria pessoa + DPIA/aval | C) só pessoas previamente cadastradas com consentimento. **Recomendação:** A/C no MVP (falha fechada); B só com aval jurídico documentado e DPIA — o gate decide.
|
||||
- **Quem opera a fila de remoção/atendimento?** **Opções:** A) comunicação/assessoria (dona do acervo), com registro no fluxo interno | B) coordenação de campanha | C) jurídico. **Recomendação:** A — quem publica/despublica atende; jurídico entra na revisão, não na fila. _(assumido — validar com produto)_
|
||||
- **Retenção do vetor de quem consulta?** **Opções:** A) zero — descartado ao fim da consulta | B) opt-in explícito do visitante para entrar no índice | C) retenção curta para antiabuso. **Recomendação:** A como padrão e B só como opt-in no escopo A/C; C rejeitada — antiabuso é contador, não biometria guardada.
|
||||
- **Revisão jurídica/DPIA é dependência dura?** **Opções:** A) DPIA + aval registrados antes de qualquer superfície pública | B) aval só para o MVP restrito (A/C) | C) abrir sem revisão. **Recomendação:** A/B — sem revisão registrada não abre; o gate decide o timing (eleição 04/10/2026).
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue [#1369](https://github.com/fsolla/teqo/issues/1369)
|
||||
- Design UI (gate): `docs/plans/busca-fotos-por-selfie-ui-design.html`
|
||||
- `docs/plans/acervo-busca-por-sentido.md` (C229 — índice próprio; implementação decide)
|
||||
- `src/components/cards/cardCutout.ts` (precedente on-device) · `src/utilities/campaignConsent.ts` + `src/lib/campaignConsentKeys.ts` (Consent fail-closed) · `src/utilities/privateMedia/` + `src/app/(frontend)/conteudos/[slug]/midia/route.ts` (privado atrás de gate) · `src/utilities/ai/rateLimit.ts` (antiabuso)
|
||||
- `AGENTS.md` — Consent/LGPD fail-closed e convenções travadas.
|
||||
|
||||
## Self-score (shaping)
|
||||
|
||||
5/5 — (1) fatia = um outcome verificável (com consentimento, achar-se nas fotos aprovadas; sem ele, recusa); (2) appetite ~2–3 dias + aval jurídico comporta consentimento, captura on-device, resultado, remoção e antiabuso, com índice/storage no plano de implementação; (3) persona, job e aceite sem jargão de stack; (4) direção no codebase é hipótese com precedentes nomeados; (5) zero decisão dura de engenharia (escopo do índice, vetor, rate limit e storage ficam para a implementação, com o gate decidindo o escopo).
|
||||
Loading…
Reference in a new issue