Tracks the app that was already in place (capture, treatment, outreach, WhatsApp integration, server, frontend, tests, docs) so future feature work can branch and use isolated worktrees.
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:3910vianode scripts/servidor.mjs, já ativo nesta sessão — arquivos estáticos (public/**) são servidos direto do disco, umnavigatecomforce: truejá pega mudanças de CSS/JS sem precisar reiniciar o processo Node. Só reiniciar o processo Node se mudar algo emsrc/**. - 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:roote@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,--borderde 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çãotoast) - Modify:
public/css/app.css:682-696(.toast)
Interfaces:
- Consumes:
document.getElementById('toast')(já existe emindex.html/login.html, idtoast). - Produces:
toast(message, ms)continua com a mesma assinatura pública — nenhum call-site emapp.js/tratamento.js/captura.js/campanhas.jsprecisa 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.jsnã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 --testuma ú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_reasonde antes desta sessão (nenhuma ação de estado foi tocada, só CSS/JS de apresentação) — checar viafetch('/api/campaigns').