# Registro de Decisões A11Y (Memória de Padrões)

> Memória entre turnos das escolhas entre **alternativas igualmente conformes**.
> Padrão de referência: [A11Y.md](https://github.com/fecarrico/A11Y.md/blob/main/docs/pt-BR/A11Y.md) v2.0.1

**Projeto:** Site da AmigUX
**Perfil de conformidade:** ⚖️ Standard (AA)

## Decisões

- **Perfil de conformidade** → Standard (AA) — padrão do A11Y.md, adotado por suposição declarada porque a construção começou sem o perfil especificado. Site público de comunidade, sem indústria regulada. Trocar para Shield (AAA) é decisão do Felipe e muda contraste (7:1), fonte mínima (14px) e alvo (44px). (15/09/2026)

- **Filtro de categoria do Circuito** → grupo de `<button aria-pressed>` dentro de `<div role="group" aria-label>`, não `tablist` — os botões filtram uma lista que continua na página, não trocam painéis. Padrão ARIA composto sem necessidade seria *Half-Climbed ARIA Ladder*. (15/09/2026)

- **Lista filtrada** → o resultado do filtro é anunciado por uma região `aria-live="polite"` com a contagem ("12 eventos"), e os itens escondidos saem com o atributo `hidden` — não com `display:none` via classe, para que a remoção da árvore de acessibilidade e da ordem de foco seja a mesma coisa. (15/09/2026)

- **Conteúdo gerado a partir de dados** → pré-renderizado no build, nunca montado no cliente — anti-padrão *Conteúdo Refém do JavaScript*. O JS da página só filtra o que já está no HTML. Sem script, a página mostra tudo. (15/09/2026)

- **Embed de terceiro (Luma)** → o `<iframe>` recebe `title`, vive dentro de uma `<section>` com cabeçalho e descrição próprios, e é **precedido** pela lista de eventos em HTML nosso mais o link direto para `lu.ma/AmigUX`. A informação nunca depende do iframe carregar. (15/09/2026)

- **Cartão de pessoa (mentor/membro)** → o link envolve só o nome, não o cartão inteiro — cartão-link engole o nome acessível e produz "link, Fulana designer sênior no Nubank mentora de carreira agende". Foto com `alt=""` porque o nome está ao lado em texto. (15/09/2026)

- **Cor como sistema de seções** → cada seção tem uma cor da marca, sempre acompanhada de rótulo em texto e de posição na página. Nenhuma informação depende só da cor (SC 1.4.1). (15/09/2026)

- **Mascote Célio** → `alt=""` quando entra como ornamento ao lado de texto que já diz a mesma coisa; `alt` descritivo quando é a única coisa na região. Cada ocorrência está marcada no template com o motivo. Decisão confirmada pelo Felipe pendente — ver REPORT.md. (15/09/2026)

- **Ícone do WhatsApp nos botões** → `<svg aria-hidden="true">` com o texto "Entrar no grupo do WhatsApp" visível ao lado. Nunca ícone sozinho com `aria-label`, que quebra SC 2.5.3 quando o rótulo falado não bate com o visível. (15/09/2026)

- **Fundo das seções construído em HSL, não por diluição** → cada seção é a cor da bolha que leva até ela, com o mesmo matiz e a mesma saturação alta, mudando só a claridade. Diluir a cor no papel (`#FFFBF5`, branco quente) até a claridade pedida destruía o matiz: o azul virava cinza. Luminância medida na página: 0,886 a 0,902 nas quatro, com 161°/41°/151° de distância de matiz entre vizinhas. (18/09/2026)

- **`--tinta-3` escurecido de `#77757A` para `#67656B`** → falhava o AA sobre todos os fundos de seção (3,9:1 a 4,2:1) em texto de 12px — data dos episódios, dia da semana no calendário, cor de hover dos links. O novo valor é o mais claro que ainda entrega 4,5:1 sobre a seção mais escura. (18/09/2026)

- **Nome do link da marca na âncora, não no `alt`** → o logo troca de imagem por esquema de cor via `display:none`, e a imagem visível no escuro tinha `alt=""`: o link ficava sem nome acessível (axe: `link-name`, serious). O nome vive em `aria-label` no `<a>` e as duas imagens têm `alt=""`. Vale para qualquer par de imagens trocado por media query. (18/09/2026)

- **Interruptor de movimento na página, não só `prefers-reduced-motion`** → SC 2.2.2 é Nível A e não isenta decoração: movimento que começa sozinho e dura mais de 5s precisa de mecanismo para pausar. A preferência do sistema é respeitada, mas não é o mecanismo — ele tem que existir no conteúdo. Um botão no hero liga `data-movimento="pausado"` no `<html>`, que para o Célio, os blobs, as fotos e as bolinhas de uma vez, e a escolha fica no `localStorage`. Isso corrige uma falha que já existia antes do mascote animado. (18/09/2026)

- **Célio não é link nem botão: é imagem descrita, fora da ordem de foco** → decisão do Felipe, em 18/09/2026, revendo a de algumas horas antes. O que o toque faz é só animação, e animação é enriquecimento para quem enxerga; como botão ele entrava na ordem de foco e anunciava um controle que não entrega nada a quem usa leitor de tela. Fica `role="img"` com descrição, num `<div>` sem `tabindex`. O que sustenta a escolha: as mesmas animações acontecem sozinhas a cada 14–30s, então ninguém perde conteúdo por não conseguir disparar; e o controle que precisa existir para teclado — o de pausa — continua sendo botão de verdade. (18/09/2026)

- **Irritado, ele não atende** → depois de três toques seguidos o Célio ginga, e durante o gingado novos toques são ignorados e o cursor volta a ser `default`. Sem isso, insistir no clique reiniciava a ação sem parar e o gesto nunca completava. (18/09/2026)

- **Sem movimento permitido, o toque ainda responde** → com a pausa ligada ou `prefers-reduced-motion: reduce`, tocar no Célio avança uma pose fixa em vez de tocar uma animação. A affordance continua de pé sem produzir movimento — o contrário de um botão que não faz nada. (18/09/2026)

## Fase 1 — páginas internas (18/09/2026)

- **Circuito: duas visões, uma fonte, e o endereço guarda o estado** → lista e calendário leem as mesmas fichas. Sem JavaScript as duas aparecem, uma depois da outra: nada some. Com JavaScript, um grupo de rádios escolhe a visão e caixas de seleção filtram; a contagem vive num `role="status"` e a URL recebe `?ver=calendario&circ=…` para o link poder ir ao grupo do WhatsApp. (18/09/2026)

- **Estado da data só onde o dado sustenta** → "confirmada" quando há data e nenhuma ressalva; "a confirmar" quando a apuração marcou `conferir`; "sem data" quando não há mês. Não existe "prevista" porque nada na apuração de 15/09 permite afirmar isso. O estado aparece em texto, nunca só em cor. (18/09/2026)

- **Stickers do Célio parados por padrão** → os 32 são webp animados em laço, e o SC 2.2.2 não isenta decoração. A página mostra o primeiro quadro de cada um; um botão com `aria-pressed` liga todos, a escolha fica no `localStorage`, e `prefers-reduced-motion` nunca liga sozinho. O download é sempre do arquivo animado. Cada `alt` descreve a pose. (18/09/2026)

- **Embeds com o conteúdo por fora** → as playlists do YouTube e o calendário do Luma entram como `<iframe title="…">` **depois** da lista em HTML próprio, que é o que continua funcionando onde o iframe é bloqueado. As capas dos episódios são hospedadas no próprio site: o visitante não faz pedido ao YouTube só por abrir a página. (18/09/2026)

- **Relatório do censo como documento próprio** → entra em `/censo/` como está, com o CSS e a identidade dele, mais uma barra fina de volta ao site. Misturar o CSS dos dois quebraria os dois. (18/09/2026)

- **Link de pular para o conteúdo** → toda página tem `<a class="pular" href="#conteudo">` como primeiro foco; o `<main id="conteudo">` é o alvo. No hero da home, o primeiro foco depois dele é a marca. (18/09/2026)

## Fase 2 — área do membro (18/09/2026)

- **Entrar por link de e-mail, sem senha** → a pessoa digita o e-mail do censo; se ele existe, um token de uso único (32 bytes, hash SHA-256 no banco, 24 horas) vai por e-mail. A tela de resposta é **a mesma exista ou não a conta**, para ninguém descobrir quem está no censo testando endereços. Um pedido a cada dois minutos por conta. (18/09/2026)

- **Formulários: rótulo explícito, ajuda e erro ligados por `aria-describedby`** → cada campo tem `<label for>`; a ajuda tem `id` e entra no `aria-describedby`; no erro, `aria-invalid="true"`, a mensagem com `role="alert"` entra no mesmo `aria-describedby`, e o campo ganha borda mais grossa, não só cor. `novalidate` no `<form>`: a validação é do servidor, com mensagens em português que dizem o que fazer. `autocomplete` nos campos que têm token padrão (e-mail, apelido, cidade, estado, cargo, URL). (18/09/2026)

- **A teia de skills é SVG com `role="img"`, e o dado está na tabela** → regra do guia de gráficos: a alternativa acessível de um gráfico é o dado. A teia tem `<title>` e `<desc>` (via `aria-labelledby`) com cada eixo em texto; os filhos ficam `aria-hidden`; a tabela logo abaixo — que é também o formulário — traz nível, média da comunidade e as duas marcações. Você contra a média se distingue por traço cheio e tracejado, não só por cor. (18/09/2026)

- **Nível de 1 a 5 como grupo de rádios por habilidade** → cada linha é um `<fieldset>` com `<legend>` só para leitor de tela ("Nível em Pesquisa qualitativa") e cinco rádios com `aria-label` "3, Intermediário". O número visível fica `aria-hidden` para não ser lido duas vezes. Alvo de 40×40 dentro de célula de tabela; abaixo de 640px a tabela vira blocos. (18/09/2026)

- **Visibilidade nasce "só membros" e o e-mail não sai do banco** → quem autorizou no censo um diretório visível para a comunidade entra como "membros"; quem não autorizou, "privado"; ninguém nasce "público". O e-mail mora só em `contas`, que nenhuma página lê para exibir. O LinkedIn só aparece com o consentimento marcado. (18/09/2026)

- **Apagar a conta apaga de verdade** → `DELETE` com cascata em skills, sessões, tokens, interesses e conta; a confirmação é a palavra APAGAR digitada, com rótulo e ajuda, não um diálogo modal. Não há cópia guardada, e a página diz isso antes do botão. (18/09/2026)

- **Sessão em cookie `HttpOnly`, `Secure`, `SameSite=Lax`, com hash no banco** → o valor do cookie nunca é gravado; só o SHA-256. O CSRF é um HMAC da sessão com o segredo do servidor, em campo oculto, conferido em todo POST. Sair é POST. (18/09/2026)

- **Páginas da conta fora dos buscadores** → `<meta name="robots" content="noindex">` em tudo sob `/conta/`, e `Disallow: /conta/` no `robots.txt`. As páginas públicas geradas do banco (`/membros/`, `/mentoria/`) são indexáveis: só têm quem escolheu público. (18/09/2026)

## Páginas internas, segunda direção (18/09/2026)

- **Calendário como `<table>` de semanas** → cada mês é uma tabela com `<caption>` (nome do mês, ano e o que acontece no mês sem dia fechado), `<th scope="col">` com os dias da semana abreviados em `<abbr title>`, e uma célula por dia. Evento que dura vários dias aparece em cada dia; nos dias seguintes ao primeiro, o nome vai só para leitor de tela ("continua"), para não repetir visualmente. Estado da data em cor **e** em legenda de texto. A linha do tempo continua sendo a visão principal e a alternativa completa. (18/09/2026)

- **Filtro por circuito em botões `aria-pressed`, visão em rádios** → os chips são `<button type="button">` num `role="group"` rotulado; o pressionado é o filtro ativo. A escolha de visão é um `<fieldset>` de rádios com `<legend>` para leitor de tela. A contagem resultante vai para um `role="status"`. Endereço guarda `?ver=` e `?circ=`. (18/09/2026)

- **Barras do censo com o número em texto** → cada barra é um `<li>` com rótulo, o número em texto e a barra `aria-hidden`: a barra é o desenho, o dado está na linha. A lista recebe `aria-label` com a unidade ("em número de pessoas"). É a regra do guia de gráficos: a alternativa acessível do gráfico é o dado. (18/09/2026)

- **Números grandes do censo em `<dl>`** → cada par é `<dd>` com o número e `<dt>` com o que ele conta; a ordem visual (número em cima) é só CSS. O "de 45" fica em texto pequeno ao lado do número, para o leitor de tela não anunciar 41 sem denominador. (18/09/2026)

- **Poses do Célio com `alt` descritivo** → nos valores, cada pose ilustra um valor, e o `alt` descreve a pose ("Célio piscando um olho, segurando uma caneca verde com um coração"), não repete o valor, que já está no `<h3>` ao lado. (18/09/2026)

## Privacidade e calendário (18/09/2026, noite)

- **Os números do censo saem do site aberto** → decisão do Felipe, repetida: aqueles dados são de quem já está na comunidade, não de qualquer pessoa na internet. Saíram da página `/comunidade/`, o relatório saiu de `/censo/`, o link saiu do rodapé e do sitemap, e os agregados foram para `dados/censo-interno.json`, marcado como não publicável. A área de membros pode mostrá-los atrás do login; o site aberto diz apenas que o censo existiu. (18/09/2026)

- **Calendário próprio em vez de FullCalendar** → medido antes de decidir, com axe-core sobre o FullCalendar 6 puro: duas violações (contraste de 3,69:1 no texto do evento e dois `role="img"` sem nome acessível), dois botões de navegação sem nome, `<th>` sem `scope`, e apenas quatro elementos focáveis no mês inteiro — os eventos não são alcançáveis por teclado a menos que tenham URL. Além disso, ele monta a grade em JavaScript, o que deixaria o conteúdo refém do script. O calendário daqui é `<table>` pré-renderizada, cada evento é link focável, e sem JavaScript os vinte e um meses aparecem inteiros. (18/09/2026)

- **Chamada de trabalho é período, não evento diário** → pintar todos os dias de uma janela de um mês transformava fevereiro de 2027 numa grade de listras azuis. Cada chamada marca só o dia que abre e o dia que fecha, com rótulo "abre:" e "fecha:", e a janela inteira fica resumida numa faixa acima da grade. Janelas projetadas para 2027 aparecem como "janela prevista". (18/09/2026)

- **Busca com `role="search"` e resultado anunciado** → o formulário do Circuito é `role="search"` com `aria-label`; o campo tem `<label>` visível e `aria-describedby` apontando para a contagem, que vive num `role="status"`. Sem resultado, um aviso com botão para limpar. Os chips de categoria são `aria-pressed`, e a navegação de mês move o foco para o título do mês novo. (18/09/2026)

- **As poses do pôster do Célio saíram do site** → são referência de conceito, sem canal alfa; recortá-las com flood fill deixou contorno sujo, e o Felipe pegou. Os cinco valores viraram texto até existir a arte exportada com transparência. (18/09/2026)
