Files
GustavoandClaude Sonnet 5 417da56bd6 chore: import maquina-comercial-pessoal history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

git-subtree-dir: maquina-comercial-pessoal
git-subtree-mainline: f5af038e19
git-subtree-split: a9ef4d776d
2026-09-28 15:50:17 -03:00

12 KiB

Redesign visual: paleta SaaS dark (app inteiro) Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Trocar a identidade visual inteira do app (login + 4 abas + modais) de uma paleta clara/laranja para uma paleta escura única estilo SaaS (Linear/Vercel), com o card de campanha ganhando um tratamento "Dispatch Board" (borda de status colorida + números tabulares monoespaçados), e o toast passando a indicar sucesso/erro por cor.

Architecture: public/css/app.css é quase inteiramente orientado por variáveis CSS (:root); a troca de paleta é uma substituição do bloco de tokens + remoção do bloco @media (prefers-color-scheme: dark) (o app deixa de ter modo claro). Três gradientes hardcoded (parceiros do laranja atual) viram um novo token --accent-light. Tratamento "Dispatch Board" é CSS adicional em .campaign-card e nos números de estatística. Toast colorido é uma pequena mudança em public/js/api.js (inferência pelo prefixo "Erro" na própria mensagem) + 2 classes CSS novas — sem tocar nos ~40 call-sites de toast(...) espalhados pelos arquivos JS.

Tech Stack: HTML/CSS/JS vanilla (sem framework de build), Node --test, Playwright.

Global Constraints

  • Este repositório NÃO tem HEAD/commits e commits não são permitidos neste projeto — nenhuma etapa deste plano inclui git commit. Ignore os passos de commit do template padrão de planos.
  • Não há testes automatizados de aparência visual. A verificação "passou" de cada tarefa é: (a) suíte existente 100% verde, (b) checagem visual ao vivo no navegador via Claude Browser.
  • Servidor de dev roda em http://127.0.0.1:3910 via node scripts/servidor.mjs, já ativo nesta sessão — arquivos estáticos (public/**) são servidos direto do disco, um navigate com force: true já pega mudanças de CSS/JS sem precisar reiniciar o processo Node. Só reiniciar o processo Node se mudar algo em src/**.
  • Design aprovado está em docs/superpowers/specs/2026-07-30-dark-saas-redesign-design.md — os valores exatos de cor vêm de lá, não reinvente.

Task 1: Trocar os tokens de cor em app.css para a paleta SaaS dark única

Files:

  • Modify: public/css/app.css:1-47 (bloco :root e @media (prefers-color-scheme: dark))
  • Modify: public/css/app.css:80 (.brand-monogram)
  • Modify: public/css/app.css:189 (.btn-primary)
  • Modify: public/css/app.css:520 (.progress-fill)
  • Modify: public/css/app.css:587 (.rank-fill, .funil-fill)

Interfaces:

  • Produces: todo o resto do CSS (que já usa var(--x) em quase tudo) herda a nova paleta automaticamente. Nenhuma outra tarefa precisa saber os valores exatos — só que os tokens --page, --surface, --ink, --accent, --good, --warning, --critical, --radius*, --shadow* continuam existindo com os mesmos nomes.

  • Step 1: Substituir o bloco :root (linhas 2-26) por estes valores

:root {
  --page: #0b0c10;
  --page-2: #0d0e13;
  --surface: #16171d;
  --surface-2: #1d1f27;
  --ink: #f4f4f6;
  --ink-2: #a4a6b0;
  --muted: #6c6e79;
  --grid: #2a2c35;
  --border: rgba(255, 255, 255, 0.08);
  --accent: #6a63f6;
  --accent-light: #9d97ff;
  --accent-2: #4f8cff;
  --accent-ink: #ffffff;
  --bar: #6a63f6;
  --good: #22c55e;
  --good-text: #4ade80;
  --warning: #f5a524;
  --critical: #fb5b7c;
  --critical-bg: rgba(251, 91, 124, 0.12);
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  --shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
  --shadow-soft: 0 8px 24px rgba(0, 0, 0, 0.3);
}
  • Step 2: Apagar inteiramente o bloco @media (prefers-color-scheme: dark) { ... } que vem logo depois (linhas 28-47 do arquivo original)

O app passa a ter uma única aparência, sempre escura — não há mais um segundo bloco de tokens para modo claro/escuro.

  • Step 3: Trocar as 3 cores hardcoded que combinavam com o laranja antigo pelo novo --accent-light

Em .brand-monogram:

.brand-monogram { background: linear-gradient(135deg, var(--accent), var(--accent-light)); color: #fff; font-weight: 900; letter-spacing: -0.06em; box-shadow: var(--shadow-soft); }

Em .btn-primary:

.btn-primary { background: linear-gradient(135deg, var(--accent), var(--accent-light)); border-color: transparent; color: var(--accent-ink); }

Em .progress-fill:

.progress-fill { height: 100%; width: 3%; background: linear-gradient(90deg, var(--accent), var(--accent-light)); border-radius: 999px; transition: width 0.4s; }

Em .rank-fill, .funil-fill:

.rank-fill, .funil-fill { height: 100%; min-width: 3px; border-radius: 999px; background: linear-gradient(90deg, var(--bar), var(--accent-light)); }
  • Step 4: Rodar a suíte de testes existente para confirmar que nada de lógica quebrou

Run: cd "D:/PROJETOS/COMERCIAL GTI/MAQUINA COMERCIAL PESSOAL" && node --test Expected: todos os testes continuam passando (a suíte não valida cor/CSS, só comportamento — uma troca de paleta não deveria derrubar nenhum). Preste atenção especial ao teste interface e arquivos operacionais usam somente a identidade pessoal (tests/personal-branding.test.mjs) — ele lê public/css/app.css procurando strings de marca "Grupo GTI"; os valores hex novos não contêm essas strings, então deve continuar passando.

  • Step 5: Checagem visual ao vivo — página de login

Usando o Claude Browser: navigate para http://127.0.0.1:3910 com force: true, tirar um read_page/get_page_text e confirmar visualmente (screenshot, se a pane permitir) que o fundo está escuro (#0b0c10-ish), o card de login com fundo #16171d-ish, texto claro legível, sem nenhum resquício de fundo claro/branco aparecendo.

  • Step 6: Checagem visual ao vivo — aba Capturar Leads (já vem ativa por padrão) e aba Tratar Leads

Clicar na aba "Tratar Leads", conferir que os stat-tiles, tabela de leads, badges de classificação (excelente/bom/médio/baixo) e gráficos de ranking estão legíveis e com contraste adequado no fundo escuro novo. Não deve haver texto escuro sobre fundo escuro (herança de alguma cor que não passou a usar variável).

Não há commit nesta etapa (projeto sem git commits).


Task 2: Tratamento "Dispatch Board" nos cards de campanha e números de estatística

Files:

  • Modify: public/css/app.css (seção /* campanhas */, a partir de .campaign-cards)
  • Modify: public/css/app.css:522-525 (.stat-row .mini b)
  • Modify: public/css/app.css:562 (.tile .valor)

Interfaces:

  • Consumes: tokens --muted, --critical, --good, --radius, --border de Task 1.

  • Produces: nada que outra tarefa dependa — mudança visual isolada.

  • Step 1: Adicionar borda de status colorida à esquerda do card de campanha

Na seção /* campanhas */ de app.css, depois da regra .campaign-card { margin-bottom: 0; }, adicionar:

.campaign-card {
  border-left: 3px solid var(--muted);
  border-radius: var(--radius);
}
.campaign-card:has(.chip.warn) { border-left-color: var(--critical); }
.campaign-card:has(.chip.ok) { border-left-color: var(--good); }

(:has() funciona nos navegadores modernos — Chrome/Edge 105+, Firefox 121+ — e o card já usa .chip.warn para pausada e .chip.ok para ativa, ver public/js/campanhas.js função renderCard, então não precisa mexer no JS.)

  • Step 2: Números tabulares monoespaçados nos contadores de uso/limite e nas estatísticas
.campaign-usage span,
.stat-row .mini b,
.tile .valor {
  font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
}
  • Step 3: Rodar a suíte de testes existente

Run: node --test Expected: todos os testes continuam passando (mudança é puramente visual/CSS, nenhuma classe foi removida, .chip.warn/.chip.ok continuam existindo do mesmo jeito).

  • Step 4: Checagem visual ao vivo — aba Campanhas

Navegar para a aba Campanhas, conferir visualmente: cada card de campanha tem uma borda colorida na lateral esquerda (cinza-neutro para pronta, vermelha para pausada, verde para ativa/rodando); os números de "Manual: X/Y" e "Automático: X/Y" e o bloco de métricas (Visualizados/Aprovados/Enviados/Responderam, visível clicando "Ver fila" numa campanha) estão em fonte monoespaçada tabular. Confirmar que o texto "Pausado: ..." e os botões continuam legíveis e clicáveis.

Não há commit nesta etapa.


Task 3: Toast colorido por sucesso/erro

Files:

  • Modify: public/js/api.js:82-94 (função toast)
  • Modify: public/css/app.css:682-696 (.toast)

Interfaces:

  • Consumes: document.getElementById('toast') (já existe em index.html/login.html, id toast).
  • Produces: toast(message, ms) continua com a mesma assinatura pública — nenhum call-site em app.js/tratamento.js/captura.js/campanhas.js precisa mudar.

Por que não tem um teste automatizado nesta tarefa: tests/campaign-ui.test.mjs roda app.js/campanhas.js dentro de um vm.createContext cujo objeto de contexto já define API, el, h, dataBr, fmt como mocks manuais (não carregados de api.js). Se public/js/api.js fosse executado nesse mesmo contexto via vm.runInContext para testar o toast() real, o const API = {...} de dentro de api.js sombrearia/substituiria o API mockado que os outros ~15 testes desse arquivo dependem para interceptar chamadas de rede — quebrando a suíte inteira. Isolar só a função toast() do resto de api.js exigiria transformar o arquivo num módulo ES (export), o que está fora do escopo desta tarefa (o frontend inteiro usa <script> simples, sem import/export). Por isso a verificação aqui é só a checagem visual ao vivo do Step 3, como o próprio spec já previa.

  • Step 1: Implementar a inferência de tipo em toast()

Em public/js/api.js, substituir a função toast inteira:

function toast(message, ms = 3500) {
  const box = el('toast');
  box.textContent = message;
  box.classList.remove('hidden', 'erro', 'sucesso');
  box.classList.add(/^erro/i.test(message) ? 'erro' : 'sucesso');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => box.classList.add('hidden'), ms);
}
  • Step 2: Rodar a suíte inteira, confirmando que a mudança em api.js não quebrou nada

Run: cd "D:/PROJETOS/COMERCIAL GTI/MAQUINA COMERCIAL PESSOAL" && node --test Expected: todos os testes continuam passando — api.js não é carregado pelo harness de teste (ver nota acima), então essa mudança não deveria afetar nenhum teste existente; isso só confirma que nada mais no repo depende do comportamento antigo de toast().

  • Step 3: Estilizar as duas variantes no CSS

Em app.css, na regra .toast { ... }, adicionar logo depois:

.toast.sucesso { background: var(--surface-2); color: var(--good-text); border: 1px solid color-mix(in srgb, var(--good) 45%, transparent); }
.toast.erro { background: var(--surface-2); color: var(--critical); border: 1px solid color-mix(in srgb, var(--critical) 45%, transparent); }

(A regra base .toast { background: var(--ink); color: var(--page); } continua existindo em app.css — essas duas classes sobrescrevem background/color especificamente para os dois estados coloridos; sem .sucesso/.erro, nunca deve sobrar um toast sem uma das duas, já que toast() sempre aplica uma.)

  • Step 4: Checagem visual ao vivo de um toast de cada tipo

No navegador (Claude Browser), disparar uma ação que gera um toast de sucesso real (ex.: clicar em "Preparar fila" numa campanha) e conferir visualmente que o toast aparece esverdeado; depois provocar um erro real (ex.: tentar uma ação inválida) e conferir que fica avermelhado. Usar document.getElementById('toast').className via javascript_tool pra confirmar a classe aplicada sem depender só do visual.

Não há commit nesta etapa.


Verificação final (depois das 3 tarefas)

  • Rodar node --test uma última vez — suíte completa 100% verde.
  • Rodar npx playwright test tests/browser/campaign-pilot.spec.mjs --reporter=line — 42/42.
  • Passar pelas 4 abas + modal de Configurações no navegador real, conferindo que não sobrou nenhum resquício de fundo/texto claro (herança de alguma regra que não usava variável).
  • Confirmar que as campanhas reais do usuário continuam com o mesmo status/pause_reason de antes desta sessão (nenhuma ação de estado foi tocada, só CSS/JS de apresentação) — checar via fetch('/api/campaigns').