shipping gogrowth.me: um terminal, um jogo e uma equipe de IAs
30 minutos. do dominio vazio ao site no ar com formulario de contato seguro, jogo em canvas, email transacional funcionando, headers de segurança no nivel de quem se importa, e um visual q nao parece site generico de consultor. feito em kunshan, as 5 da manha, com codex, claude code e claude design como time de engenharia. diario de bordo da coisa toda. tudo q foi quebrado, decidido, aprendido no caminho.
o contexto
eu precisava de uma porta de entrada. 15 anos de marketing, 2 anos morando dentro do ecossistema de IA da china, e nenhum lugar na internet pra quem me procurasse entender oq eu faço. velocidade importava, nao pelo prazo, mas pelo sinal. se eu vendo velocidade com IA e demoro 3 meses pra fazer uma landing page, alguma coisa ta errada no argumento.
o briefing q eu dei pra mim mesmo foi: nada de template. nada de framework pesado. nada de stack q eu tenha q explicar antes de explicar o produto. html, css, um punhado de js. hospedagem em edge. email via api. o minimo de cola possivel entre a ideia e o deploy.
a estética: um terminal de kunshan
primeira decisao foi a mais forte: o site nao ia parecer um site. ia parecer um terminal. tipografia monoespaçada (JetBrains Mono), fundo preto, tinta vermelha saturada pro acento, scanlines sutis via ::before, vinheta radial via ::after, e uma linha de chrome no topo imitando prompt de shell: phelipe@kunshan:~$.
o sistema de cor ficou inteiro em 4 variaveis:
/* :root, tudo sai daqui */ --bg: #000; /* preto absoluto */ --ink: #ededed; /* texto principal */ --dim: #9a9a9a; /* metadados, legendas */ --dimmer: #6e6e6e; /* chrome, linhas tracejadas */ --red: #ff1a1a; /* acento, glow, CTAs */
detalhe nao obvio: a primeira versao dos cinzas (#6a6a6a, #3a3a3a) tava brigando com o preto em monitor sem calibração. textos de meta sumindo. subi tudo em uns dois notches e as bordas tracejadas de 6% pra 12% de opacidade. ajuste em dois valores e a pagina inteira ficou legivel. a moral: contraste é regra, nao gosto.
o chrome fixo no topo com o ponto vermelho pulsando (animation: pulse 1.6s ease-in-out infinite) é o detalhe q define o tom. nao é decoração, é a promessa visual de q o site foi feito por alguem q se importa. a identidade kunshan · 昆山 · 31°N 120°E no rodapé reforça o posicionamento: brasileiro, na china, operando de dentro.
great wall runner: o jogo em canvas
consultor com landing page cheia de "vamos conversar" é ruído. eu queria algo q fizesse a pessoa ficar. resposta: um endless runner de 560x340 pixels com a muralha da china rolando em parallax. teclado space ou tap na tela, vc pula obstaculos, score sobe, game over, retry.
o herói é um sprite em 3 estados (idle, correndo, pulando) distribuidos em 3 sheets de 130×198px por frame. motor de animação é um requestAnimationFrame simples com contador de tick dividindo frames: frame = Math.floor(tick / 6) % RUN_FRAMES. zero biblioteca.
a fisica cabe em 5 numeros:
const GRAVITY = 0.62; // puxando de volta ao chão const JUMP_V = -11.5; // velocidade inicial do pulo const JUMP_HOLD_G = 0.30; // segurar space = pulo mais alto const MAX_HOLD = 14; // teto do hold em ticks const GROUND_Y = H - 72; // piso fixo
o truque q dá "peso" ao pulo é o jump buffering: enquanto vc segura space, a gravidade aplicada é menor (JUMP_HOLD_G) até estourar MAX_HOLD. solta antes, pula baixo. segura, pula alto. é o mesmo truque do super mario bros de 1985. funciona pq espelha a intenção do dedo, nao o tempo do frame.
obstaculos sao gerados com gap aleatorio dentro de uma janela q encolhe conforme o score sobe. velocidade do chão acelera a cada 500 pontos. game over restaura idle e o corredor volta a respirar. o arquivo game.js tem 572 linhas. sem frameworks, sem deps, sem build step.
a equipe: três IAs e um humano
isso aqui é o ponto q mais pesa pro leitor q quer replicar. eu nao codei sozinho. mas tb nao fui passageiro. foi um time de 4: três modelos especializados e eu no comando, cada um fazendo aquilo q faz melhor:
orquestração foi humana. o segredo nao é "eu usei claude". é eu sabia oq queria. prompt ruim vira codigo ruim. prompt especifico, com constraints, referencias e criterios de aceite, vira codigo q vc comita sem revisar linha por linha. aí sim vira leverage.
o endpoint de contato: /api/contact
cada form na internet é uma porta. a maior parte ta destrancada. a minha nao.
a função vive em functions/api/contact.js, hospedada no edge do cloudflare. 5 camadas antes de chegar no resend:
- origin allowlist: só aceita request de
gogrowth.meouwww.gogrowth.me. qualquer outra origem leva 403 na cara. - content-type + content-length: só
application/json, corpo < 20KB. fora disso, 415 ou 413. - honeypot: o form tem um campo
websiteescondido via CSS. bot q preenche tudo cai nele e o servidor responde 200 silenciosamente sem nunca mandar email. o bot acha q ganhou. - cloudflare turnstile: siteverify com o token, IP do cliente anexado, falhou? 403. widget invisivel, o humano nem vê.
- sanitização:
stripControlspra remover CRLF (evita header injection no email),escapeHtmlem todos os campos antes de montar o HTML, regex de email simples como ultimo filtro.
só depois de passar pelas 5 camadas é q o resend recebe a chamada. from é sempre o endereço configurado no dominio, nunca o input do usuario. reply_to é o email de quem mandou, entao eu aperto "responder" e falo direto com a pessoa, sem expor o endereço real do form.
turnstile via api, sem abrir o painel
esta parte foi o momento mais divertido do build. normalmente vc cria um widget de turnstile abrindo o dashboard do cloudflare, clicando em "add site", copiando sitekey e secret. eu quis fazer por API pq se eu nao consigo automatizar, eu nao controlo.
gerei um token custom com escopo Turnstile:Edit. verifiquei:
curl https://api.cloudflare.com/client/v4/user/tokens/verify \ -H "Authorization: Bearer cfut_***" → { "success": true, "status": "active" }
chamei o endpoint de criação:
POST /accounts/{id}/challenges/widgets { "name": "gogrowth.me", "domains": ["gogrowth.me", "www.gogrowth.me"], "mode": "managed", "bot_fight_mode": true } → { sitekey: "0x4AAAAA...", secret: "0x4AAAAA..." }
troquei o placeholder no HTML pelo sitekey real, guardei o secret via wrangler pages secret put TURNSTILE_SECRET, deployei. pronto. widget funcionando em producao, invisivel pro usuario, e o fluxo inteiro reproduzivel, posso recriar em outro dominio com 3 comandos.
segurança: o arquivo _headers
o cloudflare pages respeita um arquivo _headers na raiz do projeto. tudo q entra ali vira header em producao. o meu:
Content-Security-Policy: default-src 'self', scripts só do proprio dominio e do turnstile, nada inline de fonte externa,object-src 'none',upgrade-insecure-requests. fecha XSS no sentido certo.Strict-Transport-Security: 1 ano, includeSubDomains, preload. sem volta pra http.X-Frame-Options: SAMEORIGIN+frame-ancestors 'self': ninguem me põe em iframe pra clickjacking.Referrer-Policy: strict-origin-when-cross-origin: nao vaza path pra cross-origin.Permissions-Policy: camera, microfone, geo, pagamento, usb, floc: tudo off. um site estatico nao precisa de nada disso.Cross-Origin-Opener-Policy: same-origin: proteção contra cross-window attacks.X-Content-Type-Options: nosniff: o browser respeita o content-type q eu mandei.
resultado: A+ em securityheaders.com na primeira tentativa. nao é dificil. é só nao ignorar.
o deploy: uma linha
o ritual de deploy é esse aqui, inteiro:
npx wrangler pages deploy . \ --project-name=gogrowth \ --branch=main \ --commit-dirty=true ✨ Deployed to https://gogrowth.pages.dev ✨ Custom domain gogrowth.me attached
30 segundos do comando até o curl gogrowth.me responder com o HTML novo. sem CI, sem github actions, sem workflow yaml. é overkill pra site estatico. wrangler + git como backup local ja resolve, complexidade só entra quando tem mais de uma pessoa no repo.
iterações: contraste, guiafreela, "hire me"
depois do primeiro deploy, duas dobras importantes. a primeira foi o ajuste de contraste q mencionei lá em cima, textos dim sumindo em monitor sem calibracao. a correcao foi cirurgica: duas variaveis CSS, duas bordas dashed subiram de opacidade, uma iteracao de deploy.
a segunda foi o callout de hire me. no lançamento eu tinha só o form generico. mas se o site é sobre consultoria, "vamos conversar" é sinal fraco. precisava da oferta: $250 USD/hora, oq é, quem é o publico. callout com borda vermelha à esquerda, gradiente suave, preço grande em vermelho, CTA outline q inverte no hover. linguagem direta:
mesma operacao pra adicionar guiafreela.com na lista de labs, um dos outros projetos q eu toco. terceira iteracao, terceiro deploy, mesmo dia.
lições em voz alta
- estetica é argumento. um site q parece feito por alguem q se importa comunica antes de qualquer palavra.
- velocidade é posicionamento. se eu vendo velocidade com IA, o site tinha q ser feito rapido. tempo é o hands-on review.
- sem framework, sem build step. html estatico + um js vanilla + um edge function em js. tudo q da pra nao colocar, nao coloque.
- segurança como default. CSP, HSTS, CORS allowlist, honeypot, turnstile, escape HTML, strip de CRLF. nao é paranoia, é o basico q a maior parte dos sites ignora.
- IAs sao ferramentas, nao autores. o gosto é humano. a decisao do q entra e do q fica de fora é humana. o deploy é humano. o modelo ajuda, nao substitui.
- automatize tudo q possa repetir. criar o turnstile via API parece firula. mas é oq permite recriar o stack inteiro em outro dominio em 5 minutos quando o proximo projeto aparecer.
o site ta no ar. o jogo roda. o email chega. os headers estao apertados. a proxima pessoa q entrar em gogrowth.me às 3 da manhã vai encontrar um terminal, um corredor pulando na muralha, e uma oferta clara. foi feito em 30 min por uma pessoa num apartamento em kunshan. com IA, a ferramenta mudou, o oficio continua o mesmo.
se vc quiser conversar sobre aplicar esse mesmo tipo de velocidade e IA pro seu produto ou stack, a porta é direta: phelipe@gogrowth.me.