diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..cce8440 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "sinapse-frontend", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev", "--prefix", "frontend"], + "port": 5173 + } + ] +} diff --git a/.gitignore b/.gitignore index cc01692..74a2cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,9 @@ n8n/local-files/* .coverage htmlcov/ .pytest_cache/ + +# Canvases gerados pelo /design — regeneráveis a partir dos .dc.html +docs/design/*.html +!docs/design/*.dc.html +docs/design/prototipo/*.html +!docs/design/prototipo/*.dc.html diff --git a/docs/backlog/Backlog_Produto_Sinapse_v1.1.pdf b/docs/backlog/Backlog_Produto_Sinapse_v1.1.pdf new file mode 100644 index 0000000..31cf4c3 Binary files /dev/null and b/docs/backlog/Backlog_Produto_Sinapse_v1.1.pdf differ diff --git a/docs/backlog/Backlog_Produto_Sinapse_v1.1_resumido.pdf b/docs/backlog/Backlog_Produto_Sinapse_v1.1_resumido.pdf new file mode 100644 index 0000000..07e3843 Binary files /dev/null and b/docs/backlog/Backlog_Produto_Sinapse_v1.1_resumido.pdf differ diff --git a/docs/backlog/build_pdf.py b/docs/backlog/build_pdf.py new file mode 100644 index 0000000..40513e8 --- /dev/null +++ b/docs/backlog/build_pdf.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 +""" +Gera o PDF do Backlog de Produto a partir dos arquivos Markdown deste diretório. + +Uso: + python3 docs/backlog/build_pdf.py # versão completa + python3 docs/backlog/build_pdf.py --resumo # só as histórias, para o cliente + +Não depende de pandoc nem weasyprint: converte para HTML e imprime com o +Chrome headless. O bloco de cenário é o ponto sensível — DADO, QUANDO e ENTÃO +precisam sair uma cláusula por linha, com a palavra-chave em coluna própria. +Rodando tudo num parágrafo, o padrão do cliente deixa de ser legível. +""" + +import html +import re +import subprocess +import sys +from pathlib import Path + +AQUI = Path(__file__).resolve().parent +CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + +VERSAO = "1.1" +DATA = "08/09/2026" + +ORDEM = [ + "README.md", + "EP-01-especificar-backlog.md", + "EP-02-preservar-conhecimento.md", + "EP-03-apoio-inteligencia-artificial.md", + "EP-04-consultar-conhecimento.md", + "EP-05-competencias-equipe.md", + "EP-06-acesso-controlado.md", +] + +# Seções do README que não vão para o cliente na versão resumida. +CORTE_RESUMO = ( + "Habilitadores técnicos", + "Pendências que afetam este backlog", + "Como este backlog é organizado", + "Definição de pronto", + "Priorização", + "Registro da revisão do cliente", +) +# No resumo, o que fica de cada item é a história — cenário e regra saem. +CORTE_ITEM = ("Descrição", "Escopo macro", "Fora de escopo", "Critérios de aceitação") + + +def inline(t): + """Formatação dentro da linha. A ordem importa: código antes de negrito.""" + t = html.escape(t) + t = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", t) # link vira só o texto + t = re.sub(r"`([^`]+)`", r"\1", t) + t = re.sub(r"\*\*([^*]+)\*\*", r"\1", t) + t = re.sub(r"(?\1", t) + t = re.sub(r"~~([^~]+)~~", r"\1", t) + return t + + +PAROU = re.compile(r"^(#{1,4}\s|\||>|-{3,}$|[-*]\s|\d+\.\s|\*\*|DADO\b|QUANDO\b|ENT\u00c3O\b|COMO UM\b|EU QUERO\b|PARA QUE\b)") + + +def continuar(linhas, i, texto): + """No Markdown a cláusula pode quebrar em duas linhas. Junta a continuação.""" + while i < len(linhas): + c = linhas[i].strip() + if not c or PAROU.match(c): + break + texto += " " + c + i += 1 + return i, texto + + +def tabela(linhas): + out = [''] + for i, ln in enumerate(linhas): + celulas = [c.strip() for c in ln.strip().strip("|").split("|")] + if i == 1 and all(set(c) <= set(":- ") for c in celulas): + continue # linha de alinhamento + tag = "th" if i == 0 else "td" + out.append("" + "".join(f"<{tag}>{inline(c)}" for c in celulas) + "") + out.append("
") + return "\n".join(out) + + +def converter(md, resumo=False): + linhas = md.split("\n") + out, i = [], 0 + pulando = False + + while i < len(linhas): + ln = linhas[i] + bruto = ln.strip() + + # --- corte de seções na versão resumida --- + if bruto.startswith("## "): + titulo = bruto[3:].strip() + pulando = resumo and ( + any(titulo.startswith(c) for c in CORTE_RESUMO) + or titulo in CORTE_ITEM + ) + if pulando and not bruto.startswith("# "): + i += 1 + continue + + if not bruto: + i += 1 + continue + + # --- régua --- + if re.fullmatch(r"-{3,}", bruto): + out.append('
') + i += 1 + continue + + # --- títulos --- + m = re.match(r"^(#{1,4})\s+(.*)$", bruto) + if m: + n = len(m.group(1)) + texto = m.group(2) + classe = "" + if n == 3 and texto.startswith("PBI-"): + classe = ' class="pbi"' + elif n == 2 and texto.startswith("FT-"): + classe = ' class="feature"' + out.append(f"{inline(texto)}") + i += 1 + continue + + # --- história do usuário: COMO UM / EU QUERO / PARA QUE --- + if bruto.startswith("COMO UM"): + bloco = [] + for chave in ("COMO UM", "EU QUERO", "PARA QUE"): + if i < len(linhas) and linhas[i].strip().startswith(chave): + resto = linhas[i].strip()[len(chave):].strip() + i += 1 + i, resto = continuar(linhas, i, resto) + i -= 1 + bloco.append( + f'
{chave}' + f'{inline(resto)}
' + ) + i += 1 + out.append('
' + "".join(bloco) + "
") + continue + + # --- cenário: cabeçalho em negrito + DADO / QUANDO / ENTÃO --- + m = re.match(r"^\*\*(Cenário[^*]*)\*\*\s*$", bruto) + if m: + titulo = m.group(1) + i += 1 + clausulas = [] + while i < len(linhas): + c = linhas[i].strip() + mk = re.match(r"^(DADO|QUANDO|ENTÃO|E)\b\s*(.*)$", c) + if not mk: + break + i += 1 + i, texto = continuar(linhas, i, mk.group(2)) + clausulas.append( + f'
{mk.group(1)}' + f'{inline(texto)}
' + ) + if resumo: + continue + if clausulas: + out.append( + f'
{inline(titulo)}
' + + "".join(clausulas) + "
" + ) + else: + out.append(f"

{inline(titulo)}

") + continue + + # --- tabela --- + if bruto.startswith("|"): + bloco = [] + while i < len(linhas) and linhas[i].strip().startswith("|"): + bloco.append(linhas[i]) + i += 1 + out.append(tabela(bloco)) + continue + + # --- citação --- + if bruto.startswith(">"): + bloco = [] + while i < len(linhas) and linhas[i].strip().startswith(">"): + bloco.append(linhas[i].strip().lstrip(">").strip()) + i += 1 + out.append('
' + inline(" ".join(bloco)) + "
") + continue + + # --- lista --- + if re.match(r"^[-*]\s+", bruto) or re.match(r"^\d+\.\s+", bruto): + ordenada = bool(re.match(r"^\d+\.", bruto)) + tag = "ol" if ordenada else "ul" + itens = [] + while i < len(linhas): + c = linhas[i].strip() + m2 = re.match(r"^[-*]\s+(.*)$", c) or re.match(r"^\d+\.\s+(.*)$", c) + if not m2: + break + i += 1 + i, texto = continuar(linhas, i, m2.group(1)) + itens.append(f"
  • {inline(texto)}
  • ") + out.append(f"<{tag}>" + "".join(itens) + f"") + continue + + # --- "Regras e observações" e os blocos de detalhe saem do resumo --- + if resumo and bruto.startswith("**Regras e observações"): + while i < len(linhas) and linhas[i].strip(): + i += 1 + continue + + # --- parágrafo (junta linhas até a próxima em branco) --- + bloco = [] + while i < len(linhas) and linhas[i].strip() and not re.match( + r"^(#{1,4}\s|\||>|-{3,}$|[-*]\s|\d+\.\s|COMO UM|\*\*Cenário)", linhas[i].strip() + ): + bloco.append(linhas[i].strip()) + i += 1 + if bloco: + out.append("

    " + inline(" ".join(bloco)) + "

    ") + + return "\n".join(out) + + +CSS = """ +@page { size: A4; margin: 20mm 18mm 18mm 18mm; } +* { box-sizing: border-box; } +body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 10.5pt; line-height: 1.55; color: #16181D; margin: 0; } + +.capa { height: 247mm; display: flex; flex-direction: column; justify-content: center; + page-break-after: always; } +.capa .marca { width: 44pt; height: 8pt; background: #ED6A32; margin-bottom: 22pt; } +.capa h1 { page-break-before: avoid; font-size: 30pt; line-height: 1.12; margin: 0 0 10pt 0; color: #0B0D10; + border: 0; padding: 0; letter-spacing: -0.4pt; } +.capa .sub { font-size: 13pt; color: #6B7280; margin: 0 0 34pt 0; } +.capa .meta { font-size: 10.5pt; color: #16181D; line-height: 2; } +.capa .meta b { display: inline-block; width: 88pt; color: #6B7280; font-weight: 600; } + +h1 { font-size: 19pt; line-height: 1.2; margin: 0 0 14pt 0; padding-bottom: 7pt; + border-bottom: 2.5pt solid #ED6A32; color: #0B0D10; page-break-before: always; + page-break-after: avoid; } +h2 { font-size: 13.5pt; margin: 20pt 0 8pt 0; color: #0B0D10; page-break-after: avoid; } +h2.feature { border-left: 3pt solid #ED6A32; padding-left: 8pt; } +h3 { font-size: 11.5pt; margin: 16pt 0 7pt 0; color: #0B0D10; page-break-after: avoid; } +h3.pbi { background: #F7F7F8; border-left: 2.5pt solid #C4C7CC; padding: 6pt 9pt; + margin-left: -2pt; } +h4 { font-size: 10.5pt; margin: 12pt 0 5pt 0; color: #16181D; page-break-after: avoid; } + +p { margin: 0 0 8pt 0; } +code { font-family: "SF Mono", Menlo, monospace; font-size: 9pt; white-space: nowrap; + background: #F1F2F4; padding: 1pt 3pt; border-radius: 2pt; color: #9A4318; } +strong { font-weight: 600; } +del { color: #9A9DA4; } +hr { border: 0; border-top: 0.5pt solid #DDDFE2; margin: 14pt 0; } + +ul, ol { margin: 0 0 8pt 0; padding-left: 16pt; } +li { margin-bottom: 3pt; } + +blockquote { margin: 9pt 0; padding: 8pt 11pt; background: #FBF6F3; + border-left: 2.5pt solid #F1885B; font-size: 10pt; color: #3A3D44; } + +table { width: 100%; border-collapse: collapse; margin: 9pt 0 12pt 0; + font-size: 9.5pt; page-break-inside: avoid; } +th { background: #F1F2F4; text-align: left; font-weight: 600; color: #16181D; + padding: 5pt 7pt; border: 0.5pt solid #DDDFE2; } +td { padding: 5pt 7pt; border: 0.5pt solid #DDDFE2; vertical-align: top; } + +/* --- história e cenário: uma cláusula por linha, palavra-chave em coluna --- */ +.historia { margin: 9pt 0 11pt 0; padding: 9pt 11pt; background: #FAFAFB; + border: 0.5pt solid #E4E6E9; border-radius: 3pt; page-break-inside: avoid; } +.cenario { margin: 9pt 0; padding: 9pt 11pt; border: 0.5pt solid #E4E6E9; + border-radius: 3pt; page-break-inside: avoid; } +.cen-t { font-weight: 600; font-size: 10pt; margin-bottom: 6pt; color: #0B0D10; } +.cl { display: flex; align-items: baseline; margin-bottom: 3pt; } +.cl:last-child { margin-bottom: 0; } +.kw { flex: 0 0 54pt; white-space: nowrap; font-family: "SF Mono", Menlo, monospace; + font-size: 8pt; font-weight: 700; letter-spacing: 0.3pt; color: #C2551F; } +.tx { flex: 1; font-size: 10pt; line-height: 1.5; } +""" + + +# O resumido é um documento de leitura rápida: mesma tipografia, menos respiro. +CSS_RESUMO = """ +body { font-size: 10pt; line-height: 1.45; } +h1 { font-size: 17pt; margin-bottom: 11pt; } +h2 { font-size: 12.5pt; margin: 15pt 0 6pt 0; } +h3.pbi { font-size: 10.5pt; padding: 4pt 8pt; margin: 11pt 0 4pt 0; } +p { margin: 0 0 6pt 0; } +.historia { margin: 4pt 0 8pt 0; padding: 6pt 9pt; } +.cl { margin-bottom: 2pt; } +.tx { font-size: 9.5pt; line-height: 1.4; } +table { margin: 7pt 0 9pt 0; font-size: 9pt; } +th, td { padding: 4pt 6pt; } +blockquote { margin: 7pt 0; padding: 6pt 9pt; font-size: 9.5pt; } +""" + + +def capa(resumo): + titulo = "Backlog de Produto" + sub = "Sinapse — base inteligente de requisitos" + extra = "
    Formato Histórias de usuário
    " if resumo else "" + return f""" +
    +
    +

    {titulo}

    +

    {sub}

    +
    +
    Cliente PRO4TECH
    +
    Equipe Grupo Galáticos — Fatec São José dos Campos
    +
    Versão {VERSAO} — {DATA}
    +
    Padrão Guia de Especificação de Itens de Trabalho
    + {extra} +
    +
    +""" + + +def main(): + resumo = "--resumo" in sys.argv + partes = [capa(resumo)] + + for nome in ORDEM: + caminho = AQUI / nome + if not caminho.exists(): + print(f" ! faltando: {nome}", file=sys.stderr) + continue + md = caminho.read_text(encoding="utf-8") + if nome == "README.md": + # O cabeçalho do README já está na capa. + md = md.split("---", 1)[1] if "---" in md else md + partes.append(converter(md, resumo=resumo)) + + corpo = "\n".join(partes) + css = CSS + (CSS_RESUMO if resumo else "") + doc = (f"" + f"Backlog de Produto — Sinapse v{VERSAO}" + f"{corpo}") + + sufixo = "_resumido" if resumo else "" + base = f"Backlog_Produto_Sinapse_v{VERSAO}{sufixo}" + htm = AQUI / f".{base}.html" + pdf = AQUI / f"{base}.pdf" + htm.write_text(doc, encoding="utf-8") + + subprocess.run( + [CHROME, "--headless", "--disable-gpu", "--no-pdf-header-footer", + f"--print-to-pdf={pdf}", str(htm)], + check=True, capture_output=True, + ) + htm.unlink() + print(f"{pdf.name} — {pdf.stat().st_size // 1024} KB") + + +if __name__ == "__main__": + main() diff --git a/docs/design/Componentes.dc.html b/docs/design/Componentes.dc.html new file mode 100644 index 0000000..6e9a791 --- /dev/null +++ b/docs/design/Componentes.dc.html @@ -0,0 +1,176 @@ + + + + + + + + + + + + + +
    + +
    +

    Componentes

    +

    Controles base e seus estados. Altura de 40px para controle padrão, 44px onde houver toque.

    +
    + + +
    +
    +

    Botões

    +

    Um só primário por tela. O gradiente é reservado a ele — nenhum outro elemento da interface o usa.

    +
    + +
    + +
    + + Padrão + Sobre + Foco + Desabilitado +
    + +
    + Primário +
    Salvar item
    +
    Salvar item
    +
    Salvar item
    +
    Salvar item
    +
    + +
    + Secundário +
    Descartar
    +
    Descartar
    +
    Descartar
    +
    Descartar
    +
    + +
    + Fantasma +
    Ver histórico
    +
    Ver histórico
    +
    Ver histórico
    +
    Ver histórico
    +
    + +
    + Destrutivo +
    Arquivar
    +
    Arquivar
    +
    Arquivar
    +
    Arquivar
    +
    + +
    +
    + + +
    +
    +

    Campos

    +

    O erro nunca aparece só como cor: ícone e frase junto. A frase diz o que fazer, não o que falhou.

    +
    + +
    + +
    + Título do PBI +
    Justificar a alteração
    + Comece por um verbo no infinitivo. +
    + +
    + Título do PBI +
    Justificar a alteração
    + Comece por um verbo no infinitivo. +
    + +
    + Título do PBI +
    Melhorias na tela
    + Troque por um verbo no infinitivo. +
    + +
    + Código do item +
    PBI-01.5.6
    + Gerado pelo sistema. +
    + +
    +
    + + +
    +
    +

    Etiquetas

    +

    Prioridade e situação são as duas únicas dimensões etiquetadas. Sprint e tipo de item vão como texto.

    +
    + +
    +
    + Prioridade MoSCoW +
    + Must + Should + Could +
    +
    + +
    + Situação do item +
    + Rascunho + Em revisão + Aprovado + Bloqueado +
    +
    +
    +
    + + +
    +

    Abas e seleção

    + +
    + +
    +
    + Especificação + Cenários + Histórico +
    +
    + +
    +
    + + Título começa por verbo no infinitivo +
    +
    + + História tem os três blocos +
    +
    + + Não se aplica a este item +
    +
    + +
    +
    + +
    +
    + + diff --git a/docs/design/Main.dc.html b/docs/design/Main.dc.html new file mode 100644 index 0000000..cdccda4 --- /dev/null +++ b/docs/design/Main.dc.html @@ -0,0 +1,235 @@ + + + + + + + + + + + + + +
    + +
    +
    +
    + Sinapse · PRO4TECH +
    +

    Fundamentos

    +

    Os valores abaixo vêm da folha de estilo do site da PRO4TECH. Nomes e hex são para copiar direto para o index.css do frontend.

    +
    + + +
    +

    Cor

    + +
    +

    Marca — o laranja é o único acento. Use com moderação: ação principal, item ativo, foco.

    +
    +
    +
    +
    laranja#ED6A32
    +
    +
    +
    +
    laranja-claro#F1885B
    +
    +
    +
    +
    laranja-300#F4A684
    +
    +
    +
    +
    laranja-200#F8C3AD
    +
    +
    +
    +
    gradiente135deg
    +
    +
    +
    + +
    +

    Superfícies — três níveis de profundidade, do fundo da página ao cartão elevado.

    +
    +
    +
    +
    fundo#0B0D10
    +
    +
    +
    +
    superficie#111827
    +
    +
    +
    +
    cartao#171A21
    +
    +
    +
    +
    texto-escuro#16181D
    +
    +
    +
    +
    branco#FFFFFF
    +
    +
    +
    + +
    +

    Texto e borda — a rampa cinza da marca. Contraste mínimo de 4.5:1 sobre o fundo para qualquer texto de leitura.

    +
    +
    +
    +
    texto-forte#FFFFFF
    +
    +
    +
    +
    texto#E5E7EB
    +
    +
    +
    +
    texto-fraco#A6AAB3
    +
    +
    +
    +
    texto-apagado#6B7280
    +
    +
    +
    +
    borda#C4C7CC · 14%
    +
    +
    +
    + +
    +

    Semânticas — a PRO4TECH não publica cores de estado. Estas foram derivadas do laranja em oklch: mesma luminosidade e croma, só o matiz muda, então convivem com a marca sem competir.

    +
    +
    +
    +
    sucesso#22B458 · fundo #0D371A
    +
    +
    +
    +
    atencao#C88B00 · fundo #3E2A00
    +
    +
    +
    +
    erro#F0635D · fundo #4B1D1B
    +
    +
    +
    +
    info#4097FF · fundo #132E50
    +
    +
    +
    + +

    Texto branco sobre o laranja da marca dá 3,13:1 e reprova na WCAG AA. Por isso o rótulo do botão principal é #16181D, que dá 5,67:1 sobre o mesmo laranja — a cor da marca fica intacta.

    +
    + +
    + +

    O laranja da marca fica perto do vermelho de erro no círculo cromático. Nunca use os dois lado a lado num mesmo controle — o erro sempre vem com ícone, nunca só com cor.

    +
    +
    +
    + + +
    +

    Tipografia

    +

    As duas famílias são as do site da PRO4TECH. Public Sans só em título; Inter em tudo que é interface e leitura.

    + +
    +
    + titulo-1 · 42/800 + Backlog do produto +
    +
    + titulo-2 · 28/700 + Especificar itens de trabalho +
    +
    + titulo-3 · 18/600 + Registrar a decisão sobre uma sugestão +
    +
    +
    + corpo · 15/400 + Texto de leitura contínua: descrição de item, critério de aceitação, resposta do chat. Linha entre 60 e 75 caracteres. +
    +
    + interface · 14/500 + Rótulo de campo, item de lista, botão +
    +
    + apoio · 13/400 + Texto de ajuda sob o campo, metadado, data +
    +
    + etiqueta · 11/700 + Épico · Feature · PBI +
    +
    + código · 13 + PBI-01.5.6 +
    +
    +
    + + +
    + +
    +

    Forma e profundidade

    +
    +
    +
    +
    + raio · 10px + Um só. A marca não tem segundo raio — pílula apenas em badge. +
    +
    +
    +
    +
    + sombra + 0 1px 14px rgba(0,0,0,.13) +
    +
    +
    +
    +
    + foco + Anel de 3px a 22%. Sempre visível no teclado. +
    +
    +
    +
    + +
    +

    Espaçamento

    +
    +

    Escala de 4. Dentro do controle use 8 e 12; entre blocos, 24 e 32.

    +
    +
    4
    +
    8
    +
    12
    +
    16
    +
    24
    +
    32
    +
    48
    +
    +
    +
    + +
    + +
    +
    + + diff --git a/docs/design/Padroes.dc.html b/docs/design/Padroes.dc.html new file mode 100644 index 0000000..44f9a5b --- /dev/null +++ b/docs/design/Padroes.dc.html @@ -0,0 +1,171 @@ + + + + + + + + + + + + + +
    + +
    +

    Padrões do Sinapse

    +

    Composições que só existem neste produto. Cada uma carrega uma regra do Guia de Especificação da PRO4TECH ou do PRD.

    +
    + + +
    +
    +

    Hierarquia

    +

    Épico, Feature e PBI se distinguem pelo recuo e pelo peso do código — não por cor. O nível é informação estrutural, não decorativa.

    +
    + +
    + +
    + + EP-01 + Especificar itens de trabalho + 28 PBIs +
    + +
    + + FT-01.5 + Edição e rastreabilidade + 6 PBIs +
    + +
    + PBI-01.5.6 + Justificar a alteração de um item + Must +
    + +
    + PBI-01.5.1 + Editar um item já criado + Must +
    + +
    +
    + + +
    +
    +

    Cenário de aceitação

    +

    Uma cláusula por linha, com a palavra-chave em coluna própria. Foi o erro que quase mandamos ao cliente em PDF: rodando tudo num parágrafo, o padrão deixa de ser legível.

    +
    + +
    + +
    + Cenário 1 — Registrar o motivo +
    +
    + DADO + que o item esteja aprovado +
    +
    + QUANDO + o Product Owner alterar a descrição +
    +
    + ENTÃO + o sistema pede a justificativa antes de salvar +
    +
    +
    + +
    + Cenário 4 — Dispensar em rascunho +
    +
    + DADO + que o item ainda esteja em rascunho +
    +
    + QUANDO + qualquer campo for alterado +
    +
    + ENTÃO + a alteração é salva sem pedir justificativa +
    +
    +
    + +
    +
    + + +
    + +
    +
    +

    Completude

    +

    Aprovadas sobre aplicáveis, sem pesos.

    +
    +
    +
    + 9 de 12 verificações + 75% +
    +
    +
    +
    +
    +
    + Nenhuma verificação aplicável + +
    +

    Sem verificação aplicável o indicador some. Nunca mostra 100%: seria dizer que está pronto sem ter olhado nada.

    +
    +
    + +
    +
    +

    Sugestão da IA

    +

    A regra inegociável do PRD: a IA nunca escreve sozinha. Toda sugestão mostra de onde veio e espera uma decisão explícita.

    +
    + +
    + +
    + + Sugestão · não aplicada +
    + +

    Acrescentar um cenário para o caso de o item já estar arquivado: a edição deve ser recusada antes de pedir justificativa.

    + +
    + +
    + Baseada em PBI-01.1.5 — API-2, documentação da Sprint 2 + Nenhuma outra fonte foi usada. +
    +
    + +
    + Aceitar e editar + Dispensar +
    + +
    +
    + +
    + +
    +
    + + diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000..4e87d5f --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,47 @@ +# Design System e protótipo — Sinapse + +Corresponde ao habilitador `PRE-05`. + +## O que está publicado + +| | Link | +|---|---| +| Design System | https://claude.ai/code/artifact/ebe6e1a1-8f93-4831-a45e-cc5f81d796be | +| Protótipo navegável | https://claude.ai/code/artifact/5c9bc82a-813b-417e-ae01-df6265995476 | + +O protótipo abre na **Conversa**, que é a entrada principal de dados: reunião, +transcrição e documento viram item de trabalho por ali. As outras duas abas são +**Backlog** (árvore com responsável e editor com o checklist do guia validando ao +vivo) e **Equipe** (papel, competências e tarefas de cada pessoa). + +## Origem dos tokens + +Cor, tipografia, raio e sombra foram lidos da folha de estilo do site da PRO4TECH +(`assets/css/main.css`), não aproximados visualmente: + +| Token | Valor | +|---|---| +| Acento | `#ED6A32` · claro `#F1885B` | +| Superfícies | `#0B0D10` · `#111827` · `#171A21` | +| Texto | `#FFFFFF` · `#E5E7EB` · `#6B7280` | +| Raio | `10px` — a marca tem um só | +| Sombra | `0 1px 14px rgba(0,0,0,.13)` | +| Fontes | Public Sans (título) · Inter (interface) | + +As quatro cores semânticas são a exceção: a PRO4TECH não publica cores de estado, +então foram derivadas do laranja em oklch, mantendo luminosidade (`0.676`) e croma +(`0.176`) e variando só o matiz. + +Os tokens estão aplicados em [`frontend/src/index.css`](../../frontend/src/index.css). + +## Arquivos deste diretório + +Os `.dc.html` e o `canvas.json` são a fonte. Os `.html` de saída têm ~2,4 MB cada, +são regeneráveis e por isso ficam fora do versionamento. + +Para alterar: edite o `.dc.html`, regere o canvas e republique no mesmo endereço. +Quem for mexer precisa do comando `/design` do Claude Code — ele traz o montador. + +> A edição também pode ser feita direto na página publicada, sem passar por aqui. +> Nesse caso os arquivos deste diretório ficam defasados: regere a partir da versão +> publicada antes de editar por aqui de novo. diff --git a/docs/design/canvas.json b/docs/design/canvas.json new file mode 100644 index 0000000..53912b7 --- /dev/null +++ b/docs/design/canvas.json @@ -0,0 +1,12 @@ +{ + "artboards": [ + { "file": "Main.dc.html", "title": "Fundamentos", "x": 0, "y": 0, "w": 1120, "h": 1820, "print": "flow" }, + { "file": "Componentes.dc.html", "x": 1240, "y": 0, "w": 1120, "h": 1420, "print": "flow" }, + { "file": "Padroes.dc.html", "x": 2480, "y": 0, "w": 1120, "h": 1500, "print": "flow" } + ], + "annotations": [ + { "id": "origem-tokens", "x": 0, "y": -150, "w": 520, "text": "Todos os valores de cor, fonte, raio e sombra saíram da folha de estilo do site da PRO4TECH (assets/css/main.css), não de aproximação visual.\n\nAs quatro cores semânticas são a exceção: a marca não publica cores de estado, então foram derivadas do laranja em oklch." }, + { "id": "conflito-frontend", "x": 1240, "y": -150, "w": 520, "text": "Atenção: o frontend/src/index.css que está no repo hoje usa índigo #6366f1 e ciano #06b6d4, com visual glassmorphic. Não é a identidade da PRO4TECH.\n\nAdotar este design system significa trocar aquele arquivo." } + ], + "launch": { "view": "canvas" } +} diff --git a/docs/design/prototipo/Main.dc.html b/docs/design/prototipo/Main.dc.html new file mode 100644 index 0000000..e3aae36 --- /dev/null +++ b/docs/design/prototipo/Main.dc.html @@ -0,0 +1,1338 @@ + + + + + + + + + + + + + + + +
    + + + +
    +
    +
    +
    +
    +
    + Sinapse +
    +

    A memória de requisitos da + fábrica.

    +
    +
    +
    + E-mail + +
    +
    + Senha + +
    +
    + +
    + + + + + + Preencha e-mail e senha para + entrar. +
    +
    + +
    +
    +
    + + + +
    + +
    +
    +
    +
    + Sinapse +
    + +
    +
    + + + + {{ projetoNome }} +
    +
    + + {{ s.rotulo }} + +
    +
    +
    +
    +
    + Emmanuel +
    + EG
    + +
    +
    + + + +
    +
    +

    + Projetos

    +

    Escolha um projeto para abrir o acervo, o backlog + e a equipe.

    +
    +
    + +
    +
    + {{ + p.nome }} + {{ + p.ano }} +
    +

    {{ p.desc }}

    +
    +
    + {{ + p.itens }} + itens +
    +
    + {{ + p.fontes }} + fontes +
    +
    + {{ + p.pessoas }} + pessoas +
    +
    +
    +
    +
    + + + + + Novo projeto +
    +
    +
    +
    + + + +
    + +
    +
    +
    + + +
    + + +
    + {{ + m.texto }} +
    +
    + + +
    +
    +
    +
    +

    + {{ m.texto }}

    + + +
    + De + onde veio + +
    + + + + {{ f.rotulo }} +
    +
    +
    +
    + + +
    + +
    +
    + {{ + pr.nivel }} +
    + {{ + pr.selo.texto }} +
    + {{ + pr.titulo }} + {{ pr.origem + }} + +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    + + {{ sg.texto }} + +
    +
    + +
    +
    + +
    +
    +
    + + + +
    + Responde só a partir do acervo deste projeto. Não + inventa o que não está lá. +
    +
    +
    + + +
    +
    +
    + Acervo + do projeto + {{ totalFontes }} +
    +
    + + + + + + Soltar arquivo ou gravação + PDF, DOCX, + áudio de reunião, transcrição +
    +
    +
    + +
    + + + + + + +
    + {{ a.nome }} +
    + {{ a.tipo }} + + {{ a.status }} +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + + +
    + +
    +
    + +
    +
    + +
    + + {{ + n.codigo }} + {{ + n.titulo }} + + {{ + n.dono.iniciais }} + +
    +
    + +
    +

    Nenhum item para “{{ + filtro }}”.

    +
    +
    +
    +
    + +
    + + + +
    +
    +
    + {{ + selCodigo }} + {{ + selNivel }} +
    +

    + {{ selTitulo }}

    +
    + +
    +
    + Critérios + de aceitação +
    + {{ textoNivel }} +
    + +
    + +
    + {{ + cr.n }} + {{ cr.texto + }} +
    + + + +
    +
    +
    + +
    +

    + Nenhum critério registrado ainda.

    +
    +
    +
    + +
    + + +
    +
    +
    +
    + + + +
    + +
    + {{ + selCaminho }} + {{ + selCodigo }} +
    + Must + Rascunho +
    + +
    +
    + Título + +
    +
    + Responsável +
    + DP +
    + Daniel + Product Owner +
    +
    +
    +
    + +
    + História + do usuário +
    + COMO + UM + +
    +
    + EU + QUERO + +
    +
    + PARA + QUE + +
    +
    + + +
    + +
    +
    + Cenário + {{ ce.n }} +
    + + + +
    +
    + DADO + +
    +
    + QUANDO + +
    +
    + ENTÃO + +
    +
    +
    + +
    +

    + Nenhum cenário de aceitação. Sem eles, não há como demonstrar o item pronto.

    +
    +
    + +
    + + +
    +
    + Protótipo + e referência visual +
    + PNG, JPG ou PDF +
    + + +
    +
    + + + + + +
    +
    + {{ an.nome }} + {{ an.legenda }} +
    + +
    +
    + + +
    + + + + + + O protótipo não substitui + os critérios escritos. Ele não registra regra de negócio, exceção nem estado de erro — + escreva ao menos um cenário. +
    +
    + + + + +
    + +
    + + +
    + +
    +
    +
    + + +
    +
    +
    + Completude + {{ + percent }} +
    +
    +
    +
    +
    + {{ resumo }} +
    +
    + +
    + + + + + + + + + + + + + + +
    + {{ c.texto }} + + {{ c.dica }} + +
    +
    +
    +
    +
    +
    + +
    +
    + + + +
    +
    +

    + Equipe do projeto

    +

    Quem é cada + pessoa neste projeto, o que domina e o que está sob sua responsabilidade. Importado junto com a + estrutura e as tarefas.

    +
    +
    + +
    + {{ + p.iniciais }} +
    + {{ + p.nome }} + {{ p.papel }} +
    +
    + Competências +
    + + {{ + c.nome }} + +
    +
    +
    + {{ + p.rotuloTarefas }} + +
    + + {{ + t.codigo }} + +
    +
    + + Nenhuma tarefa atribuída + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + + + + \ No newline at end of file diff --git a/docs/design/prototipo/canvas.json b/docs/design/prototipo/canvas.json new file mode 100644 index 0000000..a4c37f4 --- /dev/null +++ b/docs/design/prototipo/canvas.json @@ -0,0 +1,48 @@ +{ + "artboards": [ + { + "file": "Main.dc.html", + "title": "Sinapse — Sprint 1", + "x": 0, + "y": 0, + "w": 1440, + "h": 900, + "is_interactive": true, + "expand": "fit" + } + ], + "annotations": [ + { + "id": "como-navegar", + "x": 0, + "y": -230, + "w": 660, + "text": "Protótipo navegável, com controles que funcionam.\n\nEntre com qualquer e-mail e senha → escolha um projeto. Abre na CONVERSA, que é a porta de entrada principal: é por ali que reunião, transcrição e documento viram item de trabalho.\n\nClique “Resuma a reunião de 09/09” — a IA responde citando o trecho e propõe três itens, cada um esperando Aceitar ou Dispensar. Nenhum entra sozinho.\n\nPergunte qualquer outra coisa e ela responde que não sabe: é o guardrail do PBI-03.2.4, não uma falha." + }, + { + "id": "abas", + "x": 720, + "y": -230, + "w": 400, + "text": "As três abas no topo:\n\nCONVERSA — entrada de dados por IA e consulta ao acervo.\n\nBACKLOG — a árvore com responsável por item e o editor com o checklist do guia validando ao vivo.\n\nEQUIPE — papel de cada pessoa no projeto, competências e o que está sob sua responsabilidade." + }, + { + "id": "dados", + "x": 1520, + "y": 0, + "w": 420, + "text": "Os nomes e as atribuições da aba Equipe são reais: saíram do PLANEJAMENTO_SCRUM.md que o LoadCG subiu em 09/09.\n\nO resto é exemplo: as respostas da IA, os arquivos do acervo e a contagem dos cards de projeto." + }, + { + "id": "niveis", + "x": 1520, + "y": 300, + "w": 420, + "text": "O editor muda com o nível selecionado na árvore.\n\nClique em EP-01 ou numa FT: aparecem os critérios de aceitação em lista ordenada, com adicionar, remover e reordenar. Clique num PBI: volta o editor de história e cenários.\n\nNos cenários dá para reordenar e remover. E em \"Anexar protótipo\": assim que houver anexo, o checklist ganha uma décima verificação e avisa que imagem não substitui critério escrito — apague os cenários para ver o alerta." + } + ], + "launch": { + "view": "focused", + "file": "Main.dc.html" + } +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 229b85d..7ace147 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,10 +6,10 @@ Sinapse — Base Inteligente de Requisitos - + - +
    diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 048f070..b49dd03 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -130,7 +130,7 @@ export const App: React.FC = () => {

    Sinapse

    - PRO4TECH API-4 + PRO4TECH API-4

    Memória Institucional da Fábrica de Software diff --git a/frontend/src/auth/Auth.tsx b/frontend/src/auth/Auth.tsx index f83515a..7dd51a7 100644 --- a/frontend/src/auth/Auth.tsx +++ b/frontend/src/auth/Auth.tsx @@ -129,7 +129,7 @@ function Login() { const [error, setError] = useState(""); const [busy, setBusy] = useState(false); return

    - Sinapse + Sinapse

    Entre na sua conta

    Acesse a memória institucional da sua equipe.

    {notice &&

    {notice}

    } diff --git a/frontend/src/index.css b/frontend/src/index.css index 1e05fa4..44e0ce7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,39 +1,13 @@ -:root { - --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --font-mono: 'JetBrains Mono', monospace; +/* ============================================================ + Sinapse — base e componentes + ------------------------------------------------------------ + Os tokens vivem em styles/tokens.css, que é a fonte única. + Este arquivo só define o reset, a base do documento e as classes + de componente. Não redefina cor aqui. + ============================================================ */ - /* Color Palette - Deep Slate / Indigo Cyber */ - --bg-primary: #0a0d14; - --bg-secondary: #111622; - --bg-tertiary: #182030; - --bg-card: rgba(19, 26, 42, 0.7); - --bg-card-hover: rgba(26, 35, 58, 0.85); - - --border-subtle: rgba(255, 255, 255, 0.08); - --border-active: rgba(99, 102, 241, 0.4); - - --text-primary: #f8fafc; - --text-secondary: #94a3b8; - --text-muted: #64748b; - - --accent-primary: #6366f1; - --accent-glow: rgba(99, 102, 241, 0.25); - --accent-secondary: #06b6d4; - --accent-emerald: #10b981; - --accent-amber: #f59e0b; - - --radius-sm: 8px; - --radius-md: 12px; - --radius-lg: 16px; - --radius-full: 9999px; - - --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2); - --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.35); - --shadow-glow: 0 0 24px var(--accent-glow); - - --transition-fast: 0.15s ease; - --transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1); -} +@import './styles/tokens.css'; +@import './styles/components.css'; .auth-shell { min-height: 100vh; display: grid; place-items: center; padding: 24px; } .auth-card { width: 100%; max-width: 440px; padding: 32px; display: grid; gap: 18px; } @@ -41,7 +15,7 @@ .auth-card p { color: var(--text-secondary); } .auth-card form { display: grid; gap: 12px; } .auth-card input { width: 100%; padding: 12px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); background: var(--bg-secondary); color: var(--text-primary); font: inherit; } -.auth-card [role="alert"] { color: #fca5a5; } +.auth-card [role="alert"] { color: var(--ds-danger-fg); } button:focus-visible, input:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 3px; } button:disabled { opacity: .6; cursor: wait; } @media (max-width: 900px) { header { flex-wrap: wrap; gap: 16px; } } @@ -54,20 +28,32 @@ button:disabled { opacity: .6; cursor: wait; } body { font-family: var(--font-sans); - background-color: var(--bg-primary); - color: var(--text-primary); + background-color: var(--ds-bg-app); + color: var(--ds-text); line-height: 1.6; -webkit-font-smoothing: antialiased; min-height: 100vh; + /* Gradiente da PRO4TECH (--gradient1 na folha deles). */ background-image: - radial-gradient(circle at 15% 15%, rgba(99, 102, 241, 0.12) 0%, transparent 40%), - radial-gradient(circle at 85% 85%, rgba(6, 182, 212, 0.08) 0%, transparent 45%); + radial-gradient(circle at 10% 0%, rgba(237, 106, 50, 0.08), transparent 28%), + linear-gradient(180deg, var(--ds-surface-800) 0%, var(--ds-surface-900) 100%); background-attachment: fixed; } +h1, h2, h3 { + font-family: var(--ds-font-display); + letter-spacing: -0.01em; + line-height: 1.2; +} + a { - color: inherit; + color: var(--ds-brand-500); text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--ds-brand-400); } button { @@ -77,87 +63,206 @@ button { outline: none; } -/* Glassmorphic Container */ +/* Foco visível em qualquer elemento navegável por teclado. */ +:focus-visible { + outline: none; + box-shadow: var(--ds-ring-focus); + border-radius: var(--ds-radius); +} + +/* --- Superfície elevada ---------------------------------- */ +/* Nome mantido por compatibilidade: a PRO4TECH não usa vidro, + então isto é um cartão opaco. */ .glass-panel { - background: var(--bg-card); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - box-shadow: var(--shadow-md); - transition: transform var(--transition-normal), border-color var(--transition-normal); + background: var(--ds-bg-subtle); + border: 1px solid var(--ds-border); + border-radius: var(--ds-radius); + box-shadow: var(--ds-shadow-sm); + transition: border-color var(--transition-normal), background var(--transition-normal); } .glass-panel:hover { - border-color: var(--border-active); + border-color: var(--ds-border-strong); } -/* Badges */ +/* --- Etiquetas ------------------------------------------- */ .badge { display: inline-flex; align-items: center; + justify-self: start; + width: fit-content; gap: 6px; - padding: 4px 10px; - border-radius: var(--radius-full); - font-size: 0.75rem; - font-weight: 600; + height: 24px; + padding: 0 12px; + border-radius: var(--ds-radius-pill); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.06em; text-transform: uppercase; - letter-spacing: 0.05em; } .badge-success { - background: rgba(16, 185, 129, 0.15); - color: #34d399; - border: 1px solid rgba(16, 185, 129, 0.3); + background: var(--ds-success-bg); + color: var(--ds-success-fg); + border: 1px solid rgba(34, 180, 88, 0.42); } .badge-info { - background: rgba(99, 102, 241, 0.15); - color: #818cf8; - border: 1px solid rgba(99, 102, 241, 0.3); + background: var(--ds-info-bg); + color: var(--ds-info-fg); + border: 1px solid rgba(64, 151, 255, 0.42); } .badge-warning { - background: rgba(245, 158, 11, 0.15); - color: #fbbf24; - border: 1px solid rgba(245, 158, 11, 0.3); + background: var(--ds-warning-bg); + color: var(--ds-warning-fg); + border: 1px solid rgba(200, 139, 0, 0.42); +} + +.badge-brand { + background: var(--ds-ai-bg); + color: var(--ds-brand-400); + border: 1px solid var(--border-active); } -/* Buttons */ +.badge-danger { + background: var(--ds-danger-bg); + color: var(--ds-danger-fg); + border: 1px solid rgba(240, 99, 93, 0.42); +} + +/* --- Botões ---------------------------------------------- */ +/* Um só primário por tela. O gradiente é exclusivo dele. */ .btn-primary { - background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); - color: #fff; - padding: 10px 20px; - border-radius: var(--radius-sm); - font-weight: 500; + background: var(--ds-brand-gradient); + color: var(--ds-on-brand); + height: var(--ds-control-h); + padding: 0 18px; + border-radius: var(--ds-radius); + font-size: 14px; + font-weight: 600; display: inline-flex; align-items: center; + justify-content: center; gap: 8px; - box-shadow: var(--shadow-sm); transition: all var(--transition-normal); } .btn-primary:hover { - background: linear-gradient(135deg, #818cf8 0%, #6366f1 100%); - box-shadow: var(--shadow-glow); - transform: translateY(-1px); + background: linear-gradient(135deg, var(--ds-brand-300) 0%, var(--ds-brand-400) 100%); + box-shadow: var(--ds-shadow-sm); +} + +.btn-primary:disabled { + background: var(--ds-bg-subtle); + color: var(--ds-text-muted); + border: 1px solid var(--ds-border); + cursor: not-allowed; } .btn-secondary { - background: var(--bg-tertiary); - color: var(--text-secondary); - border: 1px solid var(--border-subtle); - padding: 10px 18px; - border-radius: var(--radius-sm); + background: var(--ds-bg-subtle); + color: var(--ds-text); + border: 1px solid var(--ds-border-strong); + height: var(--ds-control-h); + padding: 0 18px; + border-radius: var(--ds-radius); + font-size: 14px; font-weight: 500; display: inline-flex; align-items: center; + justify-content: center; gap: 8px; transition: all var(--transition-normal); } .btn-secondary:hover { - background: var(--bg-card-hover); - color: var(--text-primary); - border-color: var(--border-active); + background: var(--ds-bg-hover); + color: var(--ds-text-strong); + border-color: rgba(196, 199, 204, 0.38); +} + +.btn-ghost { + background: transparent; + color: var(--ds-text-secondary); + height: var(--ds-control-h); + padding: 0 14px; + border-radius: var(--ds-radius); + font-size: 14px; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all var(--transition-normal); +} + +.btn-ghost:hover { + background: rgba(196, 199, 204, 0.07); + color: var(--ds-text-strong); +} + +.btn-danger { + background: var(--ds-danger-bg); + color: var(--ds-danger-fg); + border: 1px solid rgba(240, 99, 93, 0.42); + height: var(--ds-control-h); + padding: 0 18px; + border-radius: var(--ds-radius); + font-size: 14px; + font-weight: 600; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all var(--transition-normal); +} + +.btn-danger:hover { + background: #5E2321; /* danger-bg elevado */ + color: var(--ds-text-strong); + border-color: rgba(240, 99, 93, 0.62); +} + +/* --- Campos ---------------------------------------------- */ +.campo { + height: var(--ds-control-h); + width: 100%; + padding: 0 14px; + border-radius: var(--ds-radius); + background: var(--ds-bg-app); + border: 1px solid var(--ds-border-strong); + color: var(--ds-text); + font-family: inherit; + font-size: 14px; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.campo::placeholder { + color: var(--ds-text-disabled); +} + +.campo:focus { + outline: none; + border-color: var(--ds-brand-500); + box-shadow: var(--ds-ring-focus); + color: var(--ds-text-strong); +} + +.campo[aria-invalid='true'] { + border-color: var(--ds-danger-fg); +} + +.campo:disabled { + background: var(--ds-bg-app); + border-color: rgba(196, 199, 204, 0.09); + color: var(--ds-text-disabled); + cursor: not-allowed; +} + +@media (prefers-reduced-motion: reduce) { + * { + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + } } diff --git a/frontend/src/projects/projects.css b/frontend/src/projects/projects.css index 212d147..6cde710 100644 --- a/frontend/src/projects/projects.css +++ b/frontend/src/projects/projects.css @@ -2,7 +2,7 @@ .projects-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; } .projects-heading h2 { font-size: 1.8rem; } .projects-heading p, .project-card p, .projects-state p, .project-form > p { color: var(--text-secondary); } -.projects-eyebrow { color: var(--accent-secondary) !important; font-size: .8rem; } +.projects-eyebrow { color: var(--ds-text-muted) !important; font-size: .8rem; letter-spacing: .06em; text-transform: uppercase; } .projects-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr)); gap: 20px; } .project-card { padding: 24px; display: grid; gap: 12px; align-content: start; overflow-wrap: anywhere; } .project-card .badge, .project-card button { justify-self: start; } @@ -16,7 +16,7 @@ .project-field input, .project-field textarea { width: 100%; background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); padding: 12px; font: inherit; } .project-field textarea { resize: vertical; } .project-field textarea:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 3px; } -.project-field [aria-invalid="true"] { border-color: #fca5a5; } -.projects-page [role="alert"] { color: #fca5a5; } +.project-field [aria-invalid="true"] { border-color: var(--ds-danger-fg); } +.projects-page [role="alert"] { color: var(--ds-danger-fg); } .project-actions { display: flex; flex-wrap: wrap; gap: 12px; } @media (max-width: 900px) { .projects-heading { align-items: flex-start; flex-direction: column; } header > div { flex-wrap: wrap; } } diff --git a/frontend/src/styles/components.css b/frontend/src/styles/components.css index e92b47f..551d545 100644 --- a/frontend/src/styles/components.css +++ b/frontend/src/styles/components.css @@ -3,7 +3,7 @@ .ds-button { min-height: 40px; padding: 0 var(--ds-space-4); - border-radius: var(--ds-radius-sm); + border-radius: var(--ds-radius); border: 1px solid transparent; font: 600 14px/20px var(--ds-font-sans); display: inline-flex; @@ -23,7 +23,7 @@ .ds-button--primary { background: var(--ds-action); - color: var(--ds-neutral-0); + color: var(--ds-on-brand); } .ds-button--primary:hover { background: var(--ds-action-hover); } @@ -69,7 +69,7 @@ .ds-textarea { width: 100%; border: 1px solid var(--ds-border-strong); - border-radius: var(--ds-radius-sm); + border-radius: var(--ds-radius); background: var(--ds-bg-surface); color: var(--ds-text); padding: 10px 12px; @@ -84,7 +84,7 @@ background: var(--ds-bg-surface); color: var(--ds-text); border: 1px solid var(--ds-border); - border-radius: var(--ds-radius-md); + border-radius: var(--ds-radius); box-shadow: var(--ds-shadow-sm); } @@ -111,7 +111,7 @@ height: 8px; overflow: hidden; border-radius: var(--ds-radius-pill); - background: var(--ds-neutral-200); + background: var(--ds-track); } .ds-progress > span { display: block; @@ -123,9 +123,9 @@ .ds-ai-suggestion { border: 1px solid color-mix(in srgb, var(--ds-ai-fg) 25%, transparent); border-left: 4px solid var(--ds-ai-fg); - border-radius: var(--ds-radius-md); + border-radius: var(--ds-radius); background: var(--ds-ai-bg); - color: var(--ds-neutral-900); + color: var(--ds-text); padding: var(--ds-space-4); } diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index b1bf139..4d1beaf 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -1,52 +1,81 @@ +/* ============================================================ + Sinapse — tokens de interface + ------------------------------------------------------------ + Origem: identidade visual da PRO4TECH. Cor, tipografia, raio e + sombra foram lidos da folha de estilo do site do cliente + (assets/css/main.css), não aproximados a partir de captura de tela. + + As quatro cores semânticas são a única exceção: a marca não + publica cores de estado. Foram derivadas do laranja #ED6A32 em + oklch — mesma luminosidade (0.676) e croma (0.176), variando só o + matiz — para conviverem com o acento sem competir com ele. + + Design system e protótipo: docs/design/ + ============================================================ */ + :root { - /* PRO4TECH / Sinapse product palette */ - --ds-brand-950: #07111f; - --ds-brand-900: #0b1626; - --ds-brand-800: #102238; - --ds-brand-700: #173653; - --ds-brand-500: #0aa7c8; - --ds-brand-400: #24bedb; - --ds-brand-300: #71d7e8; - --ds-brand-100: #ddf7fb; + /* --- Marca ---------------------------------------------- */ + --ds-brand-600: #D3754D; + --ds-brand-500: #ED6A32; /* o acento; usar com parcimônia */ + --ds-brand-400: #F1885B; + --ds-brand-300: #F4A684; + --ds-brand-200: #F8C3AD; + --ds-brand-100: #FBE1D6; + --ds-brand-gradient: linear-gradient(135deg, #F1885B 0%, #ED6A32 100%); + /* Branco sobre o laranja dá 3,13:1 e reprova na WCAG AA. Texto escuro + sobre o mesmo laranja dá 5,67:1, então a marca fica intacta. */ + --ds-on-brand: #16181D; + + /* --- Superfícies, do fundo da página ao cartão elevado --- */ + --ds-surface-900: #0B0D10; + --ds-surface-800: #111827; + --ds-surface-700: #171A21; + --ds-surface-600: #1D212A; - --ds-neutral-0: #ffffff; - --ds-neutral-50: #f8fafc; - --ds-neutral-100: #f1f5f9; - --ds-neutral-200: #e2e8f0; - --ds-neutral-300: #cbd5e1; - --ds-neutral-400: #94a3b8; - --ds-neutral-500: #64748b; - --ds-neutral-600: #475569; - --ds-neutral-700: #334155; - --ds-neutral-800: #1e293b; - --ds-neutral-900: #0f172a; + /* --- Rampa neutra da marca ------------------------------ */ + --ds-neutral-0: #FFFFFF; + --ds-neutral-100: #E5E7EB; + --ds-neutral-300: #C4C7CC; + --ds-neutral-400: #A6AAB3; + --ds-neutral-500: #898E99; + --ds-neutral-600: #6B7280; + --ds-neutral-700: #4B4F57; - --ds-success-bg: #e8f7ef; - --ds-success-fg: #157347; - --ds-info-bg: #e8f6fa; - --ds-info-fg: #087e99; - --ds-warning-bg: #fff7e6; - --ds-warning-fg: #9a6700; - --ds-danger-bg: #fdecec; - --ds-danger-fg: #b42318; - --ds-ai-bg: #eef2ff; - --ds-ai-fg: #4f46e5; + /* --- Semânticas (derivadas, ver cabeçalho) --------------- */ + --ds-success-fg: #22B458; + --ds-success-bg: #0D371A; + --ds-warning-fg: #C88B00; + --ds-warning-bg: #3E2A00; + --ds-danger-fg: #F0635D; + --ds-danger-bg: #4B1D1B; + --ds-info-fg: #4097FF; + --ds-info-bg: #132E50; + --ds-ai-fg: var(--ds-brand-400); + --ds-ai-bg: #4A200F; - --ds-bg-app: var(--ds-neutral-50); - --ds-bg-surface: var(--ds-neutral-0); - --ds-bg-subtle: var(--ds-neutral-100); - --ds-border: var(--ds-neutral-200); - --ds-border-strong: var(--ds-neutral-300); - --ds-text: var(--ds-neutral-900); - --ds-text-secondary: var(--ds-neutral-600); - --ds-text-muted: var(--ds-neutral-500); + /* --- Papéis --------------------------------------------- */ + --ds-bg-app: var(--ds-surface-900); + --ds-bg-surface: var(--ds-surface-800); + --ds-bg-subtle: var(--ds-surface-700); + --ds-bg-hover: var(--ds-surface-600); + --ds-border: rgba(196, 199, 204, 0.14); + --ds-border-strong: rgba(196, 199, 204, 0.22); + --ds-track: rgba(196, 199, 204, 0.12); + --ds-text-strong: var(--ds-neutral-0); + --ds-text: var(--ds-neutral-100); + --ds-text-secondary: var(--ds-neutral-400); + --ds-text-muted: var(--ds-neutral-600); + --ds-text-disabled: var(--ds-neutral-700); --ds-action: var(--ds-brand-500); --ds-action-hover: var(--ds-brand-400); - --ds-focus: var(--ds-brand-300); + --ds-focus: rgba(237, 106, 50, 0.22); + /* --- Tipografia ----------------------------------------- */ + --ds-font-display: 'Public Sans', Inter, ui-sans-serif, system-ui, sans-serif; --ds-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --ds-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + /* --- Espaçamento ---------------------------------------- */ --ds-space-1: 4px; --ds-space-2: 8px; --ds-space-3: 12px; @@ -58,49 +87,52 @@ --ds-space-12: 48px; --ds-space-16: 64px; - --ds-radius-sm: 8px; - --ds-radius-md: 12px; - --ds-radius-lg: 16px; - --ds-radius-pill: 999px; + /* --- Forma ---------------------------------------------- */ + /* A marca tem um raio só. Pílula apenas em etiqueta. */ + --ds-radius: 10px; + --ds-radius-pill: 9999px; - --ds-shadow-sm: 0 1px 2px rgba(15, 23, 42, .06); - --ds-shadow-md: 0 8px 24px rgba(15, 23, 42, .08); + /* --- Profundidade --------------------------------------- */ + --ds-shadow-sm: 0px 1px 14px 0px rgba(0, 0, 0, 0.13); + --ds-shadow-md: 0px 1px 14px 0px rgba(0, 0, 0, 0.13); + --ds-ring-focus: 0 0 0 3px var(--ds-focus); + + /* --- Altura de controle --------------------------------- */ + --ds-control-h: 40px; + --ds-touch-h: 44px; --ds-transition-fast: 120ms ease; --ds-transition-normal: 180ms ease; - /* aliases for the current frontend */ + /* ------------------------------------------------------------ + Compatibilidade — nomes consumidos por Auth.tsx, Projects.tsx e + App.tsx. Novo código usa os --ds-* acima. Remover quando as telas + forem migradas. + ------------------------------------------------------------ */ + --font-sans: var(--ds-font-sans); + --font-mono: var(--ds-font-mono); --bg-primary: var(--ds-bg-app); --bg-secondary: var(--ds-bg-surface); --bg-tertiary: var(--ds-bg-subtle); - --bg-card: var(--ds-bg-surface); - --bg-card-hover: var(--ds-neutral-50); + --bg-card: var(--ds-bg-subtle); + --bg-card-hover: var(--ds-bg-hover); --border-subtle: var(--ds-border); - --border-active: var(--ds-brand-300); + --border-active: rgba(237, 106, 50, 0.42); --text-primary: var(--ds-text); --text-secondary: var(--ds-text-secondary); --text-muted: var(--ds-text-muted); --accent-primary: var(--ds-action); - --accent-secondary: var(--ds-brand-400); + --accent-secondary: var(--ds-action-hover); + --accent-glow: var(--ds-focus); --accent-emerald: var(--ds-success-fg); --accent-amber: var(--ds-warning-fg); - --radius-sm: var(--ds-radius-sm); - --radius-md: var(--ds-radius-md); - --radius-lg: var(--ds-radius-lg); + --radius-sm: var(--ds-radius); + --radius-md: var(--ds-radius); + --radius-lg: var(--ds-radius); --radius-full: var(--ds-radius-pill); --shadow-sm: var(--ds-shadow-sm); --shadow-md: var(--ds-shadow-md); + --shadow-glow: var(--ds-ring-focus); --transition-fast: var(--ds-transition-fast); --transition-normal: var(--ds-transition-normal); } - -[data-theme="dark"] { - --ds-bg-app: var(--ds-brand-950); - --ds-bg-surface: var(--ds-brand-900); - --ds-bg-subtle: var(--ds-brand-800); - --ds-border: #22364c; - --ds-border-strong: #31506f; - --ds-text: var(--ds-neutral-50); - --ds-text-secondary: var(--ds-neutral-300); - --ds-text-muted: var(--ds-neutral-400); -}