Comprima e converta imagens em massa direto no navegador. Nenhum arquivo é enviado para servidor algum.
Não é uma promessa de política de privacidade: é uma propriedade da arquitetura. O site é uma exportação estática — não existe função de servidor para onde enviar nada — e há um teste que intercepta toda requisição de rede, em três navegadores, e falha se qualquer uma delas levar um byte do arquivo do usuário.
No celular o card de arquivo se dobra em duas linhas — nada é escondido, nada rola para o lado.
- Compressão em lote de JPG, PNG, WebP e AVIF, com progresso por arquivo e cancelamento a qualquer momento.
- Três modos: automático, que desce uma escada de qualidade até o arquivo ficar menor que o original; meta de tamanho, que faz busca binária na qualidade e, se ainda não couber, reduz a resolução em degraus — sem descer abaixo de 900 px no menor lado; e converter, que troca o formato sem comprimir, num decode e um encode.
- Conversão para qualquer um dos quatro formatos, ou o modo inteligente, que leva tudo para WebP e mantém AVIF como AVIF. No modo converter, PNG, WebP e AVIF saem sem perda de verdade — os pixels voltam byte a byte, e há teste que compara um a um. JPEG não tem modo sem perda, sai na qualidade máxima, e a interface diz isso.
- Uma página por par de formatos —
/jpg-para-webp,/png-para-avife mais dez — com o destino já escolhido ao abrir. Cada uma diz o que aquela conversão específica faz com o arquivo, inclusive quando ele cresce, que é o caso normal de converter para um formato mais fiel. - Três saídas: baixar um arquivo, baixar o lote em
.zip, ou salvar direto numa pasta do disco onde o navegador oferece a File System Access API. - Pastas inteiras, com a estrutura de subpastas preservada na saída.
- Comparação antes/depois com divisória arrastável — ou operável pelas setas do
teclado, porque é um
<input type="range">de verdade. - Perfis (web, e-mail, impressão) e preferências que sobrevivem à aba fechada.
- Funciona sem rede. Depois da primeira visita o app abre offline, e depois da primeira compressão ele comprime offline — os codecs ficam em cache quando são usados.
Tudo com Worker + WebAssembly: a thread principal nunca toca em pixel nenhum.
Os metadados não sobrevivem. O pipeline decodifica para pixels e recodifica do zero, então EXIF, IPTC, XMP e perfil ICC não atravessam — a coordenada de onde a foto foi tirada não sai do outro lado. A orientação é a exceção deliberada: ela não é preservada como metadado, é aplicada aos pixels, então a foto em pé sai em pé.
| Lighthouse (home) | 95 performance · 100 acessibilidade · 100 boas práticas · 100 SEO |
| JavaScript inicial | 141 KB com gzip em 8 arquivos — zero bytes de codec |
| Página inteira, primeira visita | 277 KB com gzip |
| Casco cacheado para uso offline | 832 KB — os 9,7 MB de codec entram só quando são usados |
| Lote de 3 PNGs de 1600×1200 | 11,4 MB → 1,6 MB (−86%), ~400 ms por arquivo no Chromium |
| Quantizador de PNG, 12 MP | 92 ms |
| Paridade com o app desktop | ±0,4% no modo meta, contra o Electron/sharp sobre os mesmos bytes |
| Testes | 389 de unidade e integração + 115 E2E em Chromium, Firefox e WebKit |
O Lighthouse é medido contra out/ servido com gzip, como a Vercel serve — medir sem
compressão reprova o harness, não a página. O número já foi 98 numa máquina em
repouso; os 95 acima são de uma máquina rodando build e testes. O service worker foi
medido com e sem: a diferença é de no máximo 1 ponto.
Os codecs (9,7 MB de .wasm) só são baixados quando um job produz aquele formato — o
avif_enc de 3,4 MB nunca é tocado por quem só comprime JPG.
npm install
npm run dev # http://localhost:3000npm run check # typecheck + lint + formatação + 389 testes
npm run build # exportação estática em out/
npx playwright install && npm run e2e # 115 testes nos três navegadoresRequer Node 20.9+. O npm run build usa webpack em vez de Turbopack — o motivo está em
docs/HANDOFF.md §4.
thread principal workers
───────────────── ───────
Dropzone → queueStore (Zustand)
│
QueueOrchestrator ──┐
│ │ postMessage
WorkerPool ───────┼──► image.worker × N
(núcleos E megapixels) │ decode → estratégia → resize → encode
└──► zip.worker
fflate, método stored
Quatro fronteiras, cada uma escolhida para deixar o lado de dentro testável:
| Camada | Arquivo | Testável porque |
|---|---|---|
| Estratégia | engine/image/strategy.ts |
não importa WASM, canvas nem DOM — recebe uma função render |
| Motor | engine/image/engine.ts |
os codecs entram por injeção |
| Concorrência | engine/core/pool.ts |
recebe uma fábrica de workers |
| Fila | store/queue.ts |
recebe uma fábrica de orquestrador |
É por isso que 365 dos 389 testes rodam sem carregar um byte de WebAssembly, e a concorrência inteira — orçamento de memória, cancelamento com worker travado, retentativa — é exercitada em Node. O E2E fecha por fora: o que as fronteiras permitiram testar isolado, ele prova junto, num navegador de verdade.
Detalhes em docs/ARQUITETURA.md.
| Arquivo | O que tem |
|---|---|
docs/HANDOFF.md |
Comece aqui. Estado, decisões, armadilhas e riscos abertos |
docs/ARQUITETURA.md |
As camadas, as fronteiras e por que cada uma está onde está |
docs/PLANO.md |
O plano técnico da refatoração, com as decisões justificadas |
docs/SPIKE.md |
As medições que derrubaram quatro suposições do plano |
docs/COMPARACAO-ELECTRON.md |
A paridade com o app desktop, gerada por medição |
docs/ROADMAP.md |
Fase 2 (PDF) e Fase 3 (vídeo e áudio) |
docs/PLANO-CONVERSAO.md |
Conversão de formatos: o que é viável no cliente, medido |
docs/HANDOFF-CONVERSAO.md |
O roteiro para começar a frente de conversão |
Este projeto começou como um aplicativo desktop em Electron + Sharp, preservado na tag
v1.0.0-electron e na branch legacy/electron. O
algoritmo de compressão — a escada de qualidade e a busca binária com downscale — é um
porte fiel daquele código, com duas mudanças deliberadas de comportamento, ambas
documentadas e testadas (docs/PLANO.md §3.1).
O que mudou foi o entorno: o Sharp virou WebAssembly, o processo do Electron virou um pool de workers, e o que era um instalador de 400 MB virou uma página que cabe em 141 KB.
Que as duas versões comprimem igual não é afirmação: o app desktop roda dentro da suíte de
testes, com o mesmo sharp 0.33.5 daquela tag, sobre os mesmos bytes —
docs/COMPARACAO-ELECTRON.md.
Next 16 (App Router, output: 'export') · React 19 · TypeScript 6 · Tailwind 4 ·
Zustand 5 · jSquash (mozjpeg, libwebp, libavif,
oxipng) · fflate · Vitest · Playwright
Sem analytics, sem telemetria, sem script de terceiros. As fontes são auto-hospedadas pelo
next/font justamente por isso.
MIT — Igor de Castro



