Falar com o estúdioContato
Falar com o estúdio
04/7

Serigrafia industrial

Ficha da máquina, a página que o comprador B2B abre

Imah

Site institucional B2B recriado do Figma por medição, com CMS operado pelo cliente

Cliente
Imah (via agência CADMO)
Categoria
Site institucional + CMS
Período
jul–ago 2026
Estado
Em produção
163-176-53-20.sslip.io

O problema

A Imah fabrica máquinas de serigrafia industrial e vendia por dois endereços desconexos: uma Loja Integrada com 77 máquinas e um site institucional com outras 50 páginas de produto, sem eixo comum de navegação entre eles. É venda B2B de ticket alto, em que o comprador chega por busca e compara ficha técnica antes de falar com vendedor. E ele não chegava à máquina por segmento, por tipo de equipamento nem pelo que ela imprime. Trocar uma foto, um texto ou a ordem dos produtos exigia ligar para a agência.

O que foi construído

Um app Next.js único serve o site público e o painel Payload: o texto sai renderizado no HTML e o cliente troca conteúdo, ordem e quais seções aparecem sem tocar em código, editando dentro da própria prévia. O layout foi recriado do arquivo do Figma em dois artboards (1440 e 402) escalados por `zoom`, com cada bloco carregando o id do nó de origem. O catálogo dos dois sites legados foi migrado por extração estrutural: 77 máquinas, das quais 76 com foto, 40 com ficha técnica e 33 com vídeo, que é o que a fonte tinha. Flutter Web foi descartado para o site público por decisão explícita: ele desenha texto em canvas, e num B2B que vive de busca orgânica isso é desqualificante.

77
máquinas
Catálogo da loja antiga veio inteiro
23
opções em 3 filtros
Para o visitante achar a máquina certa
0,6
px por bloco
Desvio máximo entre o site e o Figma
30
ações em português
O que dá para pedir ao assistente

Decisões de engenharia

01

O site não existia no Firefox em 8 das 10 larguras

O Firefox não implementa divisão de comprimento em `calc()` e descarta a declaração em silêncio: o `zoom: calc(100vw / 1440px)` que sustenta os dois artboards simplesmente não existia lá. Nas 10 larguras que a varredura cobria naquele dia o zoom fica 1 em todas, e só escapam 1440 e 402 (as duas larguras nativas dos artboards, onde 1 por acaso é o valor certo). Acima sobrava faixa preta; abaixo o `overflow-x: clip` cortava a página sem scrollbar, 672px num tablet de 768, com o botão de orçamento dentro. O conserto é `tan(atan2(100vw, 1440px))` num `@supports` só para o Firefox: a versão elegante, uma variável CSS única para todos os motores, foi ao ar e espremeu o site numa coluna de ~60px em celular real, então a duplicação ficou comentada para ninguém limpar de volta.

grep -n 'atan2' apps/web/src/app/globals.css · incidente de 04/08 no CLAUDE.md: Firefox 8/10 larguras quebradas, Chromium 0/10, sobre a lista de então (2560…402); hoje tools/ve.mjs varre 14 larguras nos 3 motores

02

Ferramental que mede, e trava no que não mediu

Num canvas absoluto, mover um bloco não empurra nada: ou abre buraco, ou entra por cima do vizinho. O `medir.mjs` traduz pixel de tela em pixel de artboard, mede a TINTA e não a caixa (a PP Radio Grotesk declara cap-height 0,34em, então a caixa aparada de um título de 24px mede 8,2px enquanto a tinta sobe a 21,07), lista quem está acima, abaixo e quem pinta por cima, e re-renderiza com o texto 9% e 20% maior, porque o aparelho do cliente renderiza ~9% maior que o navegador do dev. O gerador de tokens segue a mesma regra: emite 200 custom properties do JSON do Figma e escreve `BLOQUEADO` no lugar do número em qualquer escala com entrelinha `Auto` enquanto ninguém mediu com a fonte real, porque valor chutado ali passaria em qualquer diff e produziria fidelidade falsa. Sete escalas entraram nessa fila; as sete saíram medidas no navegador com Aktiv Grotesk e PP Radio Grotesk carregadas, cada uma com data, método e controle gravados no JSON. Duas seguem marcadas para re-medir quando o peso Medium chegar.

node tools/medir.mjs bloco /sobre mobile '<seletor>' · packages/tokens/build.mjs, bloco `if (v.lineHeight === "auto")` · grep -oE -- '--[a-zA-Z0-9_-]+\s*:' apps/web/src/styles/tokens.css | wc -l → 200 · 7 escalas `Auto` em packages/tokens/figma-tokens.json, as 7 com `lineHeightMedido` + campo `medicao` datado

03

Assistente que escreve no CMS pelo access do CMS

30 ferramentas (leitura, escrita, projetos, mídia, páginas, despublicação, guia) sobre o SDK da Anthropic, montadas como endpoint do Payload e não como rota solta do Next. Ali o `req` já chega com a sessão do painel resolvida, que é o que faz `overrideAccess: false` valer alguma coisa: quem decide o que o agente enxerga é a regra de access da coleção, não o endpoint. A exclusão definitiva chegou a existir e foi retirada de propósito, porque só funcionava com `overrideAccess: true`, e assim o assistente virava a única porta do sistema capaz de apagar sem ser admin. Sobrou despublicar, e apagar continua sendo pelo painel. Toda gravação vai como rascunho, a foto anexada é olhada pelo modelo antes de virar mídia (ele escreve o texto alternativo e recusa imagem que não serve) e o vídeo passa por checagem de tipo e destino.

apps/web/src/endpoints/agente.ts, registrado em payload.config.ts:132 · apps/web/src/lib/agente/ (3.528 linhas, 30 nomes únicos: 5+9+3+2+6+4+1) · remocao.ts · overrideAccess: false em remocao.ts:60,69,108

04

Catálogo migrado sem inventar campo que a fonte não tinha

77 máquinas e 131 fotos saíram da Loja Integrada; outras 50 páginas de produto do site institucional trouxeram 42 vídeos em 30 máquinas, 111 fotos e as aplicações de cada uma. A extração é estrutural, não interpretativa: `<p>` de uma linha é prosa, linha dentro de `<p>` com `<br/>` é item de lista. É isso que separa chamada, descrição e ficha técnica sem nenhum modelo adivinhando, e é por isso que o placar em produção fecha em 40 com ficha e 33 com vídeo, e não em 77 e 77. A importação é idempotente (chave = slug do nome, foto por nome determinístico) e as fotos são baixadas e reenviadas ao Cloudinary em vez de apontar para o CDN da loja antiga, que sairia do ar levando o site novo junto. As 285 aplicações viraram o terceiro filtro do catálogo, em 11 famílias que cobrem as 50 páginas de origem inteiras e hoje etiquetam 52 das 77 máquinas.

tools/importa-loja/extrai.py:136 · apps/web/src/scripts/importa-loja.ts · catalogo-loja.json → 77 produtos, 131 fotos · docs/varredura-bruto/imah-produto-conteudo.json → 50 páginas, 42 vídeos, 111 fotos, 285 aplicações · /api/equipamentos em produção: 77 total, 76 foto, 40 especificacoes, 33 video, 52 com `produz` em 11 famílias

Stack

Site público
Next.js 16.2.12 + React 19.2.4 + Tailwind 4 (App Router, dois route groups com root layouts separados)
CMS e painel
Payload CMS 3.86 no mesmo app: 6 coleções, 5 globais, 28 migrações versionadas
Banco
PostgreSQL (Docker local na 5433; em volume Docker na VM de produção)
Mídia
Cloudinary 2.10 via adaptador próprio de plugin-cloud-storage (imagem + MP4/WebM)
Design tokens
JSON extraído do Figma → gerador Node → 200 custom properties em tokens.css (alvo Dart pronto, inativo)
Verificação visual
Playwright 1.62 nos três motores (Chromium, WebKit, Firefox) + odiff-bin 3.2
Assistente do painel
@anthropic-ai/sdk 0.116, Claude Sonnet 5, 30 ferramentas com tool use
Infra
Render (blueprint render.yaml) e VM Oracle Cloud com Docker Compose + Caddy (TLS automático) e backup diário do Postgres para o OCI Object Storage (timer na VM, autenticação por instance principal)
Next.js 16Payload CMSPostgreSQLdesign tokens do Figmafidelidade medidaClaude tool useDocker + CaddySEO B2B

Telas

Catálogo com filtro por segmento, aplicação e tipo
Catálogo com filtro por segmento, aplicação e tipo
O painel: o cliente vê o site ao vivo enquanto edita
O painel: o cliente vê o site ao vivo enquanto edita
Edição visual: clicar no texto da prévia leva ao campo certo
Edição visual: clicar no texto da prévia leva ao campo certo
30 anos de inovação industrial
30 anos de inovação industrial
Projetos especiais
Projetos especiais

O papel da CG Mixart

Arquitetura, full-stack e infra, ponta a ponta: leitura literal do arquivo do Figma (REST API + painel do editor), front-end nos dois artboards, modelagem e implantação do CMS, migração dos dois sites legados, ferramental próprio de medição, deploy em VM Oracle Cloud atrás do Caddy, e os entregáveis de cliente (manual do painel em PDF, apresentação de entrega, checklist de QA). O design é do designer da CADMO: o arquivo chegou pronto e o trabalho foi recriá-lo sem drift.

Ressalvas e notas técnicas, verificadas no códigoabrir

Harness de pixel, dito por inteiro: o `tools/visual-diff` (Playwright + odiff, com dois gates, o percentual de pixels e a concentração espacial da divergência, porque 4px de deslocamento num botão dá 0,027% e passava no gate percentual) está escrito, com os dois gates implementados e o caminho sem-baseline exercitado, mas o comparador NUNCA rodou contra baseline. Exportar do Figma exigia a fonte licenciada e o cliente não tem PP Radio Grotesk Medium/Bold; `tools/visual-diff/baselines/` não existe, e o package.json da pasta ainda aponta `"baselines": "node pull-baselines.mjs"` para um arquivo que também não existe. Apagar ou implementar antes que alguém abra a pasta. A fidelidade que de fato valeu foi medição de DOM contra as coordenadas do arquivo (desvios ≤0,6px por bloco, alturas de página fechando ao px) mais varredura de geometria nos três motores. Não há CI: não existe `.github/` no repositório, e os gates (ve.mjs, medir.mjs, build, console limpo) rodam à mão. A saída Dart do gerador de tokens existe e é pulada porque `apps/mobile` não existe. A Fase 3 (app Flutter sobre a API do Payload) não começou. O site está no ar e ainda invisível para o Google de propósito: enquanto `DOMINIO_OFICIAL` seguir `null` em `apps/web/src/config/site.ts`, toda página sai com `noindex, nofollow` (conferido hoje nos dois ambientes), porque o endereço de teste da VM morre na virada. Ligar a busca é preencher o domínio da Imah depois que o DNS apontar, e fazer o deploy. O sitemap (82 URLs: 76 de máquina e 6 institucionais), o canonical e o Open Graph já saem prontos daí. Sobre a migração: as 77 máquinas vieram todas da Loja Integrada (77 produtos no arquivo de origem). Delas, 76 estão publicadas e 1 segue em rascunho (`Conjunto pinça para Garra`), e por isso o sitemap traz 76 páginas de produto, não 77. O site institucional não acrescentou máquina, ele enriqueceu o cadastro: a varredura das 50 páginas de produto do `imah.com.br` é a fonte das aplicações e dos vídeos, e de lá vêm também títulos e slugs mais completos. Hoje, em produção: 76 das 77 têm foto principal, 40 têm ficha técnica, 33 têm vídeo, 54 têm segmento e 52 têm aplicação. Ou seja, os dois filtros de conteúdo não cobrem o catálogo inteiro: 25 máquinas ficam fora do "O que você produz?" e 23 fora do "Qual seu segmento?", porque nunca tiveram página no institucional para classificá-las. Nenhuma opção de filtro, porém, volta vazia. O assistente do painel depende de `ANTHROPIC_API_KEY`, e essa chave não está prevista no deploy: não aparece em `render.yaml` (nem como `sync: false`) nem em `infra/oci/env.exemplo`. Sem ela o painel responde "O assistente não está configurado nesta instalação" (503) em `apps/web/src/endpoints/agente.ts`. Entrar com a chave da Imah é item da virada de contas, não código. Mas precisa estar na lista, senão a função mais visível do painel chega muda. O site está no ar numa VM Oracle Cloud, atrás do Caddy com TLS automático (https://163-176-53-20.sslip.io), a mesma máquina que serve o site da WHK. O deploy anterior no Render foi aposentado; o Render não é mais o ambiente do projeto. Checklist de entrega de 20 itens conferido em produção: 16 entregues, 4 parciais, 0 pendentes. Os parciais são o cadastro de Projetos Especiais pelo painel, o mesmo padrão replicado aos demais conteúdos, o letreiro animado e o orçamento direcionado ao WhatsApp. Cada incidente do projeto está registrado com a causa medida, inclusive os próprios erros: a refatoração elegante que derrubou o site no celular e o `payload run` contra o banco de produção que travou os deploys sem mensagem de erro. Cliente formal: Siebdruck Equipamentos Eireli, CNPJ 07.947.551/0001-06; contratação pela agência CADMO.