Componentes
71. HV1 — a RESPOSTA AO PONTEIRO e o PRESSIONADO DISTINTO
seed-componentes.md v1.43 · §71seção 77 de 9871-hv1-a-resposta-ao-ponteiro-e-o-pressionado.md · MD5 dcc00badTítulo completo no canon: HV1 — a RESPOSTA AO PONTEIRO e o PRESSIONADO DISTINTO · regra transversal (2026-08-19, sétima parte) · validacao/guarda-resposta-ponteiro.mjs
Escrito em 2026-08-19. Esta seção existe porque o Rafael recusou uma pergunta que eu não devia ter feito — e a recusa é uma correção de método, não de gosto.
71.1 A pergunta que criou o contrato#
Na folha do gate v13.5 eu registrei um achado e o classifiquei como "decisão de aparência, e aparência
é sua": o sino e o botão de menu (☰) da barra superior do tela-shell não mudavam nada quando o
mouse passava em cima. O Rafael respondeu:
"me questiono, nao deveria existir uma ação/efeito para cada tipo para existir essa diferenciação? o que o mercado sugere? pesquise e aplique, nao é decisao minha."
A lição, e ela é sobre a fronteira entre gosto e engenharia: eu tratei como preferência uma pergunta que a pesquisa responde. A regra deste projeto já dizia "pergunta que a pesquisa responde não é pergunta" — e eu a violei empurrando para o gate uma decisão que tem canon de mercado convergente, resposta verificável e norma aplicável. Aparência é do Rafael. CONVENÇÃO DE INTERAÇÃO é pesquisa. Distinguir as duas é o trabalho.
71.2 O RITO de três rodadas, com as fontes#
| rodada | fonte | o que ela estabelece |
|---|---|---|
| R1 canon | Material Design 3 — state layers | estado é um véu com opacidade por estado: camadas distintas, não a mesma |
| R1 canon | Adobe Spectrum — states registry | hover e down (pressionado) são estados INDEPENDENTES |
| R1 canon | eBay Playbook | hover 8%, press 12% — o pressionado é o sobrevoo mais 4%; a mudança tem de ser "subtle and distinct" e "consistent across components" |
| R2 mercado | Sainsbury's Design System | seis estados de fundação, com pressed separado de hover, os dois "a darker shade of the brand colour" — mesma direção monotônica |
| R2 mercado | Carbon (IBM) | estado interativo tem nome de token de propósito (layer-hover, background-hover, field-hover), não valor solto |
| R2 stack | shadcn/ui — o stack de produção do ERP da SEED | define hover: em todas as variantes de botão e não define active:. O stack mais usado do mercado subespecifica o pressionado — e este acervo herdou isso |
| R3 normas | WCAG 2.2 | a WCAG NÃO exige estado de sobrevoo: "WCAG does not require user interface components to have hover states" |
| R3 normas | SC 1.4.3 e SC 1.4.11 | se o estado existe, a tinta tem de manter o piso de contraste contra o NOVO fundo: ≥4,5 com texto, ≥3,0 se for só ícone |
A conclusão separa o que é norma do que é convenção — e é essa separação que o contrato carrega: HV1-a e HV1-b são convenção de mercado, canon convergente em cinco fontes, logo decisão de engenharia. HV1-c é NORMA, e é uma obrigação que só nasce porque se acrescentou o estado: quem cria um fundo novo cria a obrigação de medir o contraste sobre ele. É por isso que as três moram na mesma guarda: separá-las permitiria acrescentar hover e esquecer o contraste dele — que é exatamente a pendência CT-P7, aberta na parte anterior.
71.3 O contrato#
| # | Cláusula | Régua | Natureza |
|---|---|---|---|
| HV1-a | O controle ativo responde ao ponteiro | a captura do elemento sob :hover difere da captura em repouso |
convenção |
| HV1-b | O pressionado é distinto do sobrevoo | a captura sob :active difere da captura sob :hover |
convenção |
| HV1-c | A tinta sobrevive ao fundo novo | color × fundo resolvido ≥ 3,0 (só ícone, SC 1.4.11) ou ≥4,5 (com texto, SC 1.4.3), no sobrevoo e no pressionado |
norma |
Componente INATIVO está ISENTO e sai CONTADO. Controle disabled/aria-disabled não deve
responder ao ponteiro — exigir-lhe resposta seria exigir mentira. É a mesma isenção que a SC 1.4.3 e a
SC 1.4.11 dão ao componente inativo.
Fronteira declarada, e é DECISÃO, não economia: a guarda roda a 1440px. :hover é capacidade de
ponteiro; em aparelho de toque não existe sobrevoo, e a forma correta de tratar isso em CSS é
@media (hover: hover). Medir sobrevoo numa janela de 390px de navegador de mesa mediria uma situação
que o usuário de celular nunca vive. Cobrir toque é medir outra coisa — o pressionado por toque — e
é a pendência HV-P2.
71.4 O JUIZ É O PIXEL — e isso foi comprado com um defeito de instrumento#
A primeira versão da guarda julgava por getComputedStyle: montava uma assinatura com as propriedades
que pintam e reprovava quando ela não mudava. Na primeira execução sobre o acervo, a própria guarda se
desmentiu: o button.linha-abrir do tela-tabela saiu com assinatura idêntica entre repouso e
sobrevoo, e as duas capturas do elemento eram diferentes. A resposta visual estava fora do alcance da
assinatura — no fundo do ancestral (tr:hover pinta a linha inteira), em ::before/::after e em
descendentes mais fundos que o primeiro nível.
LIÇÃO, e ela vale para toda guarda desta casa: propriedade computada do ELEMENTO não descreve a APARÊNCIA do elemento. O que o usuário vê é a composição — ancestral, pseudo-elemento, descendente, empilhamento. A assinatura mede o que o CSS declara no nó; o contrato fala do que a tela mostra. Quando o contrato é sobre aparência, o juiz é o pixel.
A guarda captura o elemento nos três estados e compara os bytes. A assinatura de estilo continua sendo calculada, mas virou diagnóstico, e conta dois casos que o pixel sozinho não sabe nomear: "declarou e não aparece" (regra de hover que o olho não vê) e "apareceu e não declarou".
⚠ O REPOUSO é medido com o ponteiro FORA DA PÁGINA, em (-5, -5), e isso foi verificado: ali
document.querySelectorAll(':hover') devolve zero elementos, enquanto o canto inferior esquerdo do
tela-shell deixa 2 e o canto superior direito deixa 3. Repouso medido com o ponteiro dentro
da página não é repouso.
71.5 O CENSO — o tamanho real do problema, medido#
1.410 controles ATIVOS, 51 arquivos, 2 temas, 1440px, 1.410 medições efetivas, 4.230 capturas:
| cláusula | reprovações | proporção |
|---|---|---|
| HV1-a — sem nenhuma resposta ao ponteiro | 619 | 44% dos controles ativos |
| HV1-b — pressionado indistinguível do sobrevoo | 1.350 | 96% |
| HV1-c — contraste abaixo do piso no estado novo | 90 | 6% — e estes são violação de norma HOJE |
Placar: 0 PASS · 90 FAIL · 12 [n/a]. Nenhum dos 51 arquivos passa. E isso confirma, com número,
o que a rodada R2 previu: o stack de mercado subespecifica o pressionado, e o acervo herdou.
As classes mais frequentes, para que a fila de trabalho já esteja pronta:
HV1-a — button (174) · button.seed-btn (72) · summary (48) · button.no (26) · button.b-larg (24).
HV1-b — button (196) · button.seed-btn (120) · button.btn (88) · button.icell (68) · summary (48).
HV1-c — button.page-btn (28) · button.btn (22) · button.linha-abrir (20) · button.acc-trigger (12) · button.btn-focus (4) · button.pg-btn (2) · button.fachada (2).
71.6 A GRAMÁTICA — HV1-d, e os dois tokens que nasceram#
Gêmeos de token vão a v1.18. Dois semânticos de propósito, sem nenhum hex novo: os valores são os da escala de superfície que o acervo já pratica.
| token | claro | escuro | propósito |
|---|---|---|---|
--seed-action-neutral-hover |
#F2F6F9 |
#141D23 |
sobrevoo de controle neutro, sobre qualquer superfície neutra |
--seed-action-neutral-active |
#E3EBF0 |
#0B1419 |
pressionado de controle neutro — um passo adiante, na mesma direção |
Por que NÃO se reusou
action-ghost-hover, e o motivo é medido: no tema escuro ele vale#1D272D, que é exatamentesurface-raised. Um controle neutro posado sobre superfície elevada — a barra superior do shell — teria hover invisível no escuro. O token não está errado para o propósito dele (botão fantasma sobre a PÁGINA); está errado para este outro propósito. Consumidores medidos deaction-ghost-hoverhoje: 2 arquivos (banco-componentes,banco-tokens), nos dois sobre a página — nenhum sobre superfície elevada. Nada muda para eles.
Gramática por família de base:
| base do controle | :hover |
:active |
|---|---|---|
| superfície neutra | action-neutral-hover |
action-neutral-active |
marca chapada (surface-brand-strong, o .avatar) |
surface-brand-deep + text-on-brand-deep (par medido 6,32 claro / 9,37 escuro) |
o mesmo fundo + anel interno de text-on-brand-deep — muda o pixel sem trocar o fundo, o que preserva o contraste já medido |
rampa da marca (o .rail-item) |
chrome-overlay-hover |
chrome-overlay-active — já existiam, véu branco medido em composição |
Alternativa descartada 1 — véu (
box-shadow: inset), que é o mecanismo do Material Design 3. Descartada por motivo medido, não estético: os instrumentos desta casa leembackground-colorpara compor contraste (ofundoResolvidoda guarda HV1 aborta em imagem/gradiente, e ocontraste-composicaotrata gradiente como caso especial). Um véu tornaria o contraste do estado NÃO MEDÍVEL. Trocar um contrato medido por um contrato bonito é o oposto do que este projeto faz. Alternativa descartada 2 —action-primary-activeno pressionado do avatar. Medido: no tema escuro ele vale#11B0A0e o branco sobre ele mede 2,66 — reprovaria a SC 1.4.11.
71.7 O que a execução tocou nesta parte, e o defeito que a guarda pegou EM MIM#
Bloco HV1 acrescentado a shell-template.html (.topbar button, .topbar .avatar), com os quatro
nomes de token na lista NOMES do gen-shell.py. tela-shell.html regerado.
| medida | antes | depois |
|---|---|---|
HV1-a no tela-shell, por tema |
10 | 8 |
HV1-b no tela-shell, por tema |
16 | 12 |
HV1-c no tela-shell |
0 | 0 |
CT1 no tela-shell (regressão) |
8 · 0 | 8 · 0, 2 temas × 4 estados |
A guarda pegou, no mesmo minuto, um defeito que eu acabei de introduzir. A primeira versão do bloco escrevia
.avatar:hover(especificidade 0,2,0), que perde de.topbar button:hover(0,2,1). Resultado: o avatar recebia o fundo neutro e ficava com tinta branca sobre#F2F6F9— a guarda mediu 1,09 contra o piso 4,5 e reprovou na hora..topbar .avatar(0,3,1) conserta. Este é o argumento inteiro a favor de a cláusula de contraste morar na MESMA guarda que a de resposta ao ponteiro: acrescentar estado é criar fundo novo, e fundo novo pede medida — no mesmo passe, não na próxima sessão.
71.8 Pendências#
| # | Pendência | Por que fica |
|---|---|---|
| HV-P4 | A gramática HV1-d ainda não foi aplicada ao acervo inteiro: 619 casos de HV1-a e 1.350 de HV1-b seguem abertos fora do tela-shell |
É mudança transversal em ~34 artefatos e 20 moldes, e cada um exige regeneração mais medição de contraste do estado novo. A fila está pronta e impressa por classe no §71.5 — não é buraco, é lista de trabalho com número. Fazer às pressas produziria o defeito que a guarda acabou de pegar em mim, 34 vezes |
| HV-P5 | Os 90 casos de HV1-c são violação de norma HOJE e concentram-se em 7 classes (page-btn, btn, linha-abrir, acc-trigger, btn-focus, pg-btn, fachada) |
É a prioridade absoluta da próxima parte, acima de HV-P4: ali o hover já existe e já mata o contraste. Sete classes, não 1.350 controles |
| HV-P1 | <a href> sem classe de botão não entra na população |
link tem convenção própria (sublinhado) e é população inteira diferente |
| HV-P2 | toque não é medido | :hover é capacidade de ponteiro; cobrir toque é medir o pressionado por toque, que é outra régua |
| HV-P3 | a guarda compara aparência, não legibilidade da mudança | uma diferença de 1 unidade num canal de cor passa em HV1-a e é invisível ao olho. O piso perceptual não existe ainda |