Uma frente por vez, do tamanho de uma dobra. Em cima, o destaque: um card largo de duas colunas — à esquerda um número grande com o rótulo do que ele mede, a ressalva presa a ele, uma frase e três atributos com ícone de traço; à direita a foto sangrada, com a categoria e o título escritos dentro da imagem, ancorados no alto, sobre véu desfocado com máscara. No canto de baixo da foto gira o selo circular. Embaixo, a fileira de três a quatro escolhas — número, categoria, nome e frase curta —, e clicar numa delas troca o destaque: o conteúdo entra seco e só a foto atravessa, em 280ms.
O problema editorial que ela resolve não é "mostrar serviços": é dar tamanho de dobra a uma frente sem esconder as outras. Quatro frentes de assessoria em quatro colunas viram quatro cartõezinhos em que nenhuma tem foto, número nem espaço para uma frase inteira; quatro dobras empilhadas custam quatro rolagens e a quarta ninguém lê. A vitrine dá a uma delas a dobra inteira e deixa as outras três à vista como escolha, na mesma tela.
Compliance
O número grande é o ponto de compliance desta peça, e é ele que decide o que pode entrar. Um algarismo em corpo de 75px ao lado de uma foto de escritório é lido como afirmação, e num site de assessoria a afirmação mais perigosa é a de desempenho.
| Regra | O que isso quer dizer |
|---|---|
| O número mede o TRABALHO, nunca o dinheiro | prazo, frequência de revisão, comitês por ano, gerações cobertas pelo desenho, jurisdições no escopo, anos de casa, famílias atendidas. Os quatro do markup são inofensivos por construção: 90 dias, 3 gerações, 4 comitês por ano, 2 jurisdições |
| Nunca | rentabilidade, retorno, "+52%", "1,8× o CDI", "R$ 4 bi sob gestão em 3 anos", patrimônio médio de cliente — com número ou com palavra, no rótulo ou na frase |
O rótulo (.vit-num-rot) não é opcional |
é ele que diz o que o algarismo mede. Sem rótulo, "90" sozinho vira o que o leitor quiser que seja |
| Toda cifra vem com a ressalva | <p class="nota vit-nota">, logo abaixo do rótulo, separada por um filete. As quatro do markup terminam negando resultado — é esse o padrão a manter, não o texto exato |
| Nada de marca real | nem nas fotos, nem no texto. Os exemplos não citam banco, corretora, gestora nem veículo |
| Nada de cliente nomeado | a frente descreve o trabalho, não o caso de alguém |
| A foto não é de cliente | e se for de pessoa real, exige autorização de imagem: nesta dobra a foto não é creditada em lugar nenhum e não há onde pôr a ressalva |
Se a área técnica do cliente pedir um número que é de resultado, a resposta não é amaciar o texto: é outro componente. Número apurado com fonte e data mora em dados/grade-indicadores; cotação com validade declarada, em dados/cards-cotacoes.
Quando usar
- Três ou quatro frentes de atuação que merecem uma dobra cada, mas não têm página própria (ou têm, e esta dobra é o convite).
- Quando cada frente tem foto editorial boa, um número estrutural e três compromissos que se escrevem em cinco palavras. Faltando qualquer um dos três nas quatro, o componente é outro.
- Depois do hero ou no meio da página, quando a proposta já está apresentada e o que falta é o escopo.
- Quando a página não pode crescer mais: a vitrine entrega quatro dobras de conteúdo em uma dobra de altura.
- Quando a ordem das frentes não é informação. Etapas que se leem em sequência pedem
listas/linha-do-tempo-etapas.
Quando não usar
- Quando as frentes precisam ser COMPARADAS. Este é o corte que decide, e o irmão mais próximo é
conversao/planos-honorarios: três colunas lado a lado, com as linhas equivalentes alinhadas na horizontal porsubgrid. Ele ganha sempre que a pergunta for "qual destas é a minha?" — comparar exige tudo à vista, e uma aba por vez esconde três quartos da resposta. Esta vitrine ganha quando a pergunta for "o que tem dentro desta?": coluna comparável não tem largura para foto, número grande, frase e três atributos, e forçar isso nela produz três colunas que ninguém lê até o fim. Um detalhe do irmão vale como precedente e está registrado na ficha dele: a alternância dele é um grupo de radio e não um tablist, porque são dois conjuntos mutuamente exclusivos e não painéis de aba. Aqui é o contrário — cada painel é conteúdo próprio, navegável fora de ordem, que é exatamente a definição de aba. - Com cinco ou mais frentes. A fileira de quatro cai para 2×2 no celular; cinco deixam uma célula órfã, e em desktop a quinta escolha desce para 200px de largura. Com cinco a oito frentes,
listas/paineis-empilhadosoulistas/leque-paineis. - Com duas frentes. Duas abas são um interruptor, não uma vitrine — e aí o grupo de radio do
conversao/planos-honorariosé o padrão certo. - Sem foto. Metade do card é imagem sangrada. Quatro frentes só de texto são
conteudo/grid-entidadesou a grade de serviços com ícone. - Quando cada item é um link para outra página. Aqui o painel é o conteúdo, e nada dentro dele é focável. Conjunto fechado de quatro cards clicáveis é
midia/grade-cases-foto. - Acima da dobra. É peça de escopo; precisa de uma proposta antes.
- Duas vezes na mesma página. Funciona (o script renumera tudo), mas obriga a pessoa a aprender o mesmo gesto duas vezes.
por_pagina: [1,1].
Contra os vizinhos
| Precisa de | Use |
|---|---|
| Uma frente por vez, com foto, número e três compromissos | este |
| Comparar modelos lado a lado, com preço e botão | conversao/planos-honorarios |
| Conjunto fechado de quatro cards que levam a outra página | midia/grade-cases-foto |
| Cinco a oito frentes com foto, sem tamanho de dobra para cada | listas/paineis-empilhados |
| Recursos de peso desigual, com um parágrafo que abre no clique | conteudo/bento-recursos-expansivel |
| Etapas cuja ordem é a informação | listas/linha-do-tempo-etapas |
| Números apurados, com fonte e data | dados/grade-indicadores |
Padrão editorial
| Slot | Regra |
|---|---|
Olho (.eyebrow) |
2 a 3 palavras, opcional. Diz que o que vem é um conjunto: "Frentes de trabalho". |
Título (h2.sec-title) |
4 a 8 palavras, uma em <em>. Nomeia o conjunto e diz quantas são — "Quatro frentes, e o escopo de cada uma". O número na frase é o que avisa que há escolha. |
Apoio (.sec-lead) |
1 a 2 frases. Diz que há uma escolha embaixo. Sem isso, a fileira é lida como quatro cards de conteúdo, e ninguém clica. |
Categoria na foto (.vit-cat) |
12 a 28 caracteres, do mesmo conjunto fechado nas três ou quatro. Acima de 28 a linha em caixa alta com .16em de tracking encosta na borda da legenda. |
Título do destaque (.vit-titulo) |
4 a 8 palavras, dentro da foto. Diz o que a frente faz, nunca o que ela rende. As quatro do markup têm 5 a 7 palavras (28 a 36 caracteres). |
Número (.vit-num) |
1 a 3 dígitos. Ver Compliance — é o slot mais perigoso da peça. Número com separador de milhar quebra o alinhamento com a unidade. |
Unidade (.vit-num-un) |
1 a 3 palavras, na linha de base do algarismo: "dias", "gerações", "comitês por ano". Acima de três palavras ela quebra e o número deixa de ser uma peça só. |
Rótulo (.vit-num-rot) |
8 a 16 palavras. Diz o que o número mede. Não é opcional. |
Ressalva (.vit-nota) |
100 a 170 caracteres. De onde vem o número e o que ele não afirma. Uma por destaque, sempre. |
Frase (.vit-frase) |
180 a 210 caracteres, uma ou duas frases. As do markup medem 184 a 196 — a faixa é estreita de propósito (ver abaixo). |
Atributos (.vit-attr) |
exatamente 3 por destaque, de 4 a 8 palavras, no mesmo tipo gramatical. Cada um leva um ícone de traço desenhado no markup: não há biblioteca de ícones para consultar, e é assim de propósito (contrato §11). |
Escolha: número (.vit-esc-n) |
2 dígitos sempre, inclusive abaixo de dez. É o que alinha a fileira. |
Escolha: categoria (.vit-esc-cat) |
1 a 2 palavras — a categoria do painel, encurtada: "Planejamento patrimonial" vira "Planejamento". |
Escolha: nome (.vit-esc-nome) |
2 a 5 palavras. O que a frente entrega, não o nome dela de novo: "Holding, doação e testamento" ganha de "Sucessão", que a linha de cima já disse. |
Escolha: frase (.vit-esc-frase) |
40 a 60 caracteres, uma frase. Acima de 60 ela vai a quatro linhas na célula de 170px do celular, e a fileira fica mais alta que o card. |
Texto do selo (textPath) |
30 a 53 caracteres, opcional. O arco gasta ~4,38 unidades por caractere em 232,5 disponíveis; o excedente não é desenhado e some sem aviso. |
Quantidade: 3 ou 4 frentes. Quatro é o desenho de origem e o que fecha a fileira em 2×2 no celular.
As frases dos destaques no mesmo comprimento, e isso aqui é regra e não gosto. A peça não muda de altura entre uma aba e outra — quem garante isso é o visibility:hidden dos painéis, não a copy —, mas a dobra tem sempre a altura do maior painel. Uma frente com metade do texto das outras não desalinha nada: ela deixa um vão morto no pé da coluna e lê como frente de segunda classe. Medido com a copy do markup: frases de 184 a 196 caracteres, ressalvas de 105 a 141, e os quatro painéis fechando entre 517 e 537px a 1440 — os 20px de diferença vêm da ressalva do painel 2, que fecha em duas linhas em vez de três.
Especificação dos assets
São três ou quatro fotos, e nenhuma delas atravessa esta biblioteca (contrato §11). O preview usa ph-wide-2, ph-wide-1, ph-wide-4 e ph-wide-3 de showroom/demo/.
| Proporção do arquivo | paisagem 16:9, e o recorte é do CSS. A célula é quase quadrada no desktop (medido: 552×535 a 1440) e 16:10 no celular — não recorte os arquivos, ajuste o enquadramento por --vit-pos no style de cada <img> |
| Dimensão mínima | 1600 × 900. A célula maior é a do celular em coluna única (348px de largura), mas o desktop pede 552px de largura por 535 de altura de um arquivo 16:9, o que consome a altura inteira dele |
| Peso | até 260 KB cada. São três ou quatro na mesma dobra e todas carregam de uma vez — ver o gotcha do eager |
| Onde o texto cobre | o terço de cima, sempre: categoria e título vivem ali, sobre scrim mais véu desfocado. Medido, a legenda mede 142px a 1440 e 103px em 390. Nada de importante no alto: nem rosto, nem horizonte, nem o detalhe que justifica a foto |
| Onde o selo cobre | o canto inferior direito, num disco de 132px. No celular o selo sai |
| Enquadramento | assunto no terço central ou inferior. object-position padrão é 50% 50%; os quatro do markup declaram o próprio |
| O que não pode ter | texto dentro da foto (colide com a legenda); logo de instituição real; marca d'água; moldura ou sombra no arquivo — a quina vem de --radius-media |
As fotos precisam ser do mesmo tratamento. Elas não aparecem lado a lado — aparecem uma no lugar da outra, e a troca é uma fusão de 280ms. Duas fotos de temperatura diferente fazem a fusão parecer um erro de renderização, e não uma travessia. O critério objetivo é o mesmo do midia/grade-cases-foto: luminância média dos arquivos dentro de uma faixa de 20 pontos.
Como usar
<link rel="stylesheet" href="componentes/efeitos/selo-circular/estilo.css">
<link rel="stylesheet" href="componentes/conteudo/vitrine-destaque/estilo.css">
<script src="componentes/conteudo/vitrine-destaque/script.js"></script>
A primeira folha é do componente composto: o selo do canto é o efeitos/selo-circular, e nada no estilo.css daqui redesenha o disco, o texto curvo ou a volta. No preview isso é declarado em preview-css.txt (MANUAL §8). Não existe preview-js.txt porque o selo é CSS puro — não há script dele para carregar. Tirando o selo (ver Variações), a primeira linha sai junto.
Ganchos
| Gancho | Papel |
|---|---|
[data-vit] |
a raiz. O script parte daqui, e é o que ele conta para renumerar cópias |
[data-vit-fileira] |
a fileira, que é o role="tablist" |
[data-vit-esc] |
cada escolha: <a href="#id"> com role="tab" |
[data-vit-painel] |
cada destaque: role="tabpanel" |
--vit-pos |
enquadramento da foto, no style de cada <img> |
.is-live |
escrita pelo script na raiz. Liga o empilhamento dos painéis numa célula só |
.is-fora / .is-saindo / .is-entrando |
estado de cada painel na troca |
| escopo de tom | .sec-dark, .sec-deep, .sec-paper ou .sec-light na seção. Nenhuma linha de CSS muda |
Parafusos
| Variável | Padrão | O que faz |
|---|---|---|
--vit-gap |
clamp(18px,2.1vw,32px) |
vão entre as duas colunas do destaque |
--vit-alt |
clamp(380px,34vw,468px) |
piso do card. Com a copy do padrão editorial o card fecha acima disso (537px a 1440); o piso serve para copy curta, em que o card viraria uma faixa |
--vit-fusao |
.28s |
duração da fusão da foto. Bate com FUSAO no script.js — mudar um pede mudar o outro |
--vit-veil |
80% |
quanto o véu desfocado desce abaixo da legenda, em fração da altura dela |
--vit-pad |
clamp(24px,2.6vw,40px) |
respiro da coluna de texto |
--vit-cap-pad |
clamp(16px,1.8vw,26px) |
respiro da legenda dentro da foto |
Variações
- Três frentes — apague uma escolha e o painel dela, troque
repeat(4,…)porrepeat(3,…)no.vit-fileirae a queda de 980px para uma coluna: três escolhas em duas colunas deixam uma célula órfã. Estável. - Em dobra clara —
.sec-paperno lugar de.sec-dark. Nenhuma linha de CSS muda; medido nos quatro escopos (tabela em Gotchas). Estável. - Sem selo — apague o
<span class="vit-selo">e a folha do selo. Estável. - Sem número — apague o
.vit-cifrainteiro: número, rótulo e ressalva juntos. Ressalva sem número não tem o que ressalvar. Estável. - Foto à esquerda —
grid-column:1no.vit-mediaegrid-column:2no.vit-info. A ordem do DOM não muda, que é o que mantém a leitura de quem ouve. Experimental. - Duas vitrines na mesma página — funciona: o script renumera ids,
href,aria-controls,aria-labelledbye o arco do selo a partir da segunda cópia (medido: zero id duplicado no documento, e clicar numa não mexe na outra). Editorialmente caro. Experimental.
As duas rotas, e o que cada uma entrega
Com JavaScript é o padrão de abas do APG, inteiro. Medido no DOM: role="tablist" na fileira, role="tab" nas quatro escolhas com aria-selected numa só, role="tabpanel" nos quatro destaques com aria-labelledby apontando para a aba, aria-controls apontando para o painel, roving tabindex (0,-1,-1,-1 andando com a escolha) e tabindex="0" no painel visível — que o APG pede para painel sem nada focável dentro. ←/→ andam e trocam, com a fila circular; Home/End vão às pontas; o foco acompanha. Uma região aria-live="polite" anuncia a troca ("Sucessão, Holding, doação e testamento. Painel em exibição.").
Sem JavaScript a peça continua inteira, e isso foi medido com o JS desligado de verdade (--sem-js, que é quando o @media (scripting:none) do core casa): os quatro painéis visíveis e empilhados (topos em 421, 988, 1535 e 2103), o palco em opacity:1 e filter:none, as quatro escolhas na ordem de tabulação (tabIndex 0 nas quatro) e as quatro âncoras resolvendo — abrindo preview.html#vit-painel-3 a página para com o painel 3 no topo da janela, ao pixel.
Gotchas
-
Os painéis inativos saem por
visibility:hidden, e não por[hidden]. Parece o caminho errado e é o certo, por uma razão medida: comdisplay:nonea célula da grade tem a altura do painel visível, e os quatro não têm a mesma altura natural — 537px nos painéis 1, 3 e 4 e 517px no 2 a 1440, e 22px de diferença a 1024 (ali a diferença vem da frase, que ganha uma linha em dois dos quatro). A fileira de escolhas está logo abaixo do card: ela subia e descia debaixo do cursor de quem acabou de clicar. Comvisibilityos quatro continuam ocupando a célula, a dobra tem sempre a altura do maior, e a fileira não se move — medido,topda fileira em 1000px nas quatro abas. Não éopacity:0, e a diferença é toda:visibility:hiddentira o painel da árvore de acessibilidade e da ordem de foco;opacity:0deixaria um bloco invisível e tabulável, a armadilha que oplanos-honorariosjá registrava. De quebra, o painel que entra já está com layout pronto e imagem rasterizada, e a fusão vira pintura e nada mais. -
As quatro fotos são
eager, e isso não é descuido. Imagem comloading="lazy"dentro dedisplay:nonenão é buscada, e na primeira troca a fusão atravessaria para um retângulo vazio. Medido antes da correção: três das quatro apareciam emimgsSemPintarno primeiro carregamento. Imagemeagerdentro de conteúdo escondido, ao contrário, carrega normalmente. É o mesmo preço que ohero/slideshow-cinematicopagou pela mesma razão. (Com a troca paravisibilityo problema deixaria de existir sozinho — os painéis passaram a ter layout. As fotos ficameagerassim mesmo: é o que garante a peça de pé se alguém voltar a esconder painel pordisplay.) -
Os papéis de aba estão no markup; o
tabindex, não. Escrito à mão no HTML, o roving tabindex tiraria três das quatro escolhas da ordem de tabulação de quem está sem script — e aí a rota sem JavaScript, que é a razão de as escolhas serem<a href>, quebraria de verdade. O preço, declarado: sem script a fileira se anuncia como abas enquanto os quatro painéis estão todos visíveis, e oaria-selecteddescreve a intenção e não o estado. A alternativa era escreverroleeariapor script e deixar o markup mudo sobre a própria estrutura. -
A ativação é automática nas setas, e não manual (seta move,
Enterconfirma). O APG permite quando mostrar o painel é barato, e aqui é: o conteúdo já está no DOM e a troca não busca nada. Ativação manual custaria umEntera cada leitura e faria a seta não produzir efeito visível. -
A legenda é ancorada EM CIMA, ao contrário da receita da casa. O contrato §6 manda a legenda para dentro da imagem, e é o que ela faz; a ancoragem embaixo é o padrão porque o caso típico é um card de galeria. Aqui o card tem duas colunas e o número já ocupa o alto da coluna de texto: título no alto da foto deixa as duas colunas começando na mesma linha, e a metade de baixo fica para o selo e para a descida do olho até a fileira. O véu acompanhou — a máscara em gradiente vai do topo para baixo, o contrário da receita padrão.
-
O véu é um
::beforeda legenda e mede a altura DELA, não a da foto.bottomnegativo em porcentagem resolve contra a altura do.vit-cap, então o véu é sempre 1,8× o texto que ele serve. Véu medido em fração da foto é ultrapassado por um título de três linhas em tela estreita, e a última linha cai sobre a parte nítida — foi omidia/grade-cases-fotoque mediu isso, pela outra ponta. E o texto fica fora do elemento mascarado de propósito:mask-imageaplica-se aos descendentes, e um título dentro do véu apareceria esmaecido na ponta. -
backdrop-filtersem máscara em gradiente lê "tarja". Caixa reta produz borda dura contra a parte nítida da foto. E o gradiente escuro não substitui o desfoque, nem o contrário: só o gradiente deixa o texto sobre textura, só o desfoque deixa claro demais em foto clara. -
O scrim escurece de novo no pé da foto, e é pelo selo. O degradê vai de
.92no topo a.20em 70% e volta a.54embaixo. Sem esse retorno, o selo cairia sobre a parte clara de uma foto de céu aberto e o texto curvo dele sumiria. -
A superfície do card é
color-mixde--light, e não de--fg. A primeira versão misturava a tinta do escopo, o que escurece o card numa dobra clara: a tinta de realce caía a 4,25:1 sobre ele, contra os 4,59:1 que ela tem sobre o creme puro — abaixo do mínimo de 4,5 para texto normal, por uma casa. Com--lighto card clareia nos dois escopos:rgb(22,22,22)sobre o escuro da casa, e 4,63:1 no claro. Medido nos quatro escopos, com o tema da Liberta:
| Escopo | fundo do card | unidade e ícone (--accent-ink) |
ressalva (--fg-muted) |
rótulo, frase e atributos |
|---|---|---|---|---|
.sec-dark |
rgb(22,22,22) |
11,01:1 | 5,52:1 | 10,81:1 |
.sec-deep |
rgb(36,36,36) |
9,52:1 | 4,77:1 | 9,34:1 |
.sec-paper |
rgb(238,234,228) |
4,63:1 | 4,41:1 | 8,71:1 |
.sec-light |
rgb(238,234,228) |
4,80:1 | 4,32:1 | 9,07:1 |
Os dois números em negrito são da ressalva, e a conta é do --fg-muted do core — não de uma cor desta peça. A primitiva .nota define essa tinta, e ela vale hoje para dezessete componentes. Projeto que precise de AA estrito em dobra clara corrige no --fg-muted do tema, e a correção chega a todos de graça.
-
A categoria dentro da foto lê
--accentPURO; a unidade do número e o ícone leem--accent-ink. É a regra do MANUAL §3, e as duas metades importam: o que está sobre o fundo da seção lê a tinta de realce do escopo; o que está sobre foto com véu lê a cor de marca crua, porque ali o fundo real é a foto escurecida e o--accent-inkde um escopo claro cairia para ~3,5:1. Uma varredura que trocasse um pelo outro "por consistência" piora metade desta peça sem que nada acuse. -
O ícone de traço é o caso de fronteira, e ele ficou no grupo do texto. Ícone é desenho, e desenho pede 3:1, não 4,5:1 — mas é um traço de 1,4px, e 1,36:1 numa dobra clara o apagaria por completo. Ele lê
--accent-inkcomo o texto ao lado. -
Os doze ícones são desenhados no markup, um SVG por atributo. Nenhum ícone de terceiro atravessa a biblioteca (contrato §11) — a referência desta dobra usa uma família proprietária, e ela ficou lá. São
viewBox="0 0 24 24", traço de 1,4px emcurrentColor,aria-hiddenefocusable="false". Repetir o mesmo desenho em dois destaques é legítimo; o que não é legítimo é colar um ícone de fora. -
data-rvmora no.vit-palco, nunca nos painéis. Painel que nasce escondido nunca intersecta, e oIntersectionObserverdo core o deixaria emopacity:0para sempre quando a aba o revelasse — a armadilha que oplanos-honorariosmediu. E o selo tem odata-rvno invólucro pela outra razão: ele gerencia o própriotransform(o giro), e[data-rv].in{transform:none}vale (0,2,0) e congelaria a volta depois da entrada (contrato §5). -
O arco do selo é um id, e o script o renumera junto. Dois selos na mesma página com o mesmo id fazem os dois textos curvos seguirem o primeiro arco — gotcha documentado no
efeitos/selo-circular. Como os arcos são idênticos, ninguém percebe até o dia em que um selo tiver tamanho diferente do outro. O script lê o alvo do própriotextPathem vez de cravar o nome, então quem trocar o id no markup leva a correção junto. -
.vit-painel[hidden]{display:none}está lá mesmo sem a comutação usar o atributo (§6.5). Odisplay:griddo painel é regra de autor e venceria o[hidden]do navegador. Quem derruba uma frente inteira no site consumidor usa o atributo — e derrubar uma frente é esconder a aba e o painel: aba sem painel vira botão morto. -
A troca por clique chama
preventDefault. A escolha é um<a href>de verdade porque sem script ela precisa navegar; com script, navegar seria rolar a página por baixo do dedo de quem só queria trocar de aba. -
Nada anda sozinho, e é decisão. A vitrine não gira no relógio: aba que troca sozinha rouba a leitura no meio de um parágrafo, e ainda obrigaria a peça a carregar o botão de pausa da WCAG 2.2.2. Quem troca é sempre a pessoa — e por isso esta peça não usa o
.btn-play/LIB.pausaveldo core.
Movimento reduzido / toque
Movimento reduzido desliga a fusão e mantém a troca — a pessoa pediu por ela. Medido com a preferência ligada: transition-duration da foto em 0s, LIB.reduce em true, a volta do selo em animation:none (o selo desliga sozinho, no CSS dele), e a troca completa em menos de 40ms, sem painel pintado esperando o relógio. O script lê a mesma preferência e pula a fase de saída: com a transição desligada, os 280ms seriam uma pausa morta e não uma travessia. A entrada da dobra é do core e o core já a desliga.
Toque: nada aqui depende de hover. O único hover da peça é o realce de borda da escolha, e ele é acréscimo — o estado aceso sai do aria-selected, que existe com ou sem ponteiro. Medido em 390px com emulação de toque: sem estouro horizontal, a foto sobe para o topo do card (grid-row:1, topo em 438 contra 679 do texto), a fileira vira 2×2 de 170px por célula com 159px de altura, e o selo sai — num card de 350px o disco de 132px comeria um terço da foto e cairia sobre a legenda com título de três linhas.
Histórico
| Projeto | Onde | Data | O quê |
|---|---|---|---|
referência: skinboost-design-review.vercel.app |
dobra #catalogo |
2026-09-21 | Clone de referência, reescrito do zero. Nenhuma linha do original. |
O que não atravessou. three.js e GSAP/ScrollTrigger, que lá animavam a troca — aqui a travessia é uma transição de opacidade de 280ms num <img>. Os ícones Phosphor, substituídos por doze SVG desenhados no markup. Os assets de Figma, substituídos pelos placeholders de showroom/demo/. E as cores cruas, substituídas por token: a peça roda nos quatro escopos de tom sem uma variação de código.
O que entrou, e não existia no original: o padrão de abas do APG inteiro, a região aria-live, a rota sem JavaScript (no original o destaque inicial e as trocas dependem do script) e a camada de compliance — que aqui deu forma à peça, e não só ao texto. O número grande da referência é uma métrica de produto; aqui ele foi fechado em estrutura do trabalho, com ressalva presa e uma regra escrita no componente.json que proíbe qualquer cifra de desempenho.
Decisões da tradução para o contrato:
| Tentação | Aqui | Por quê |
|---|---|---|
[hidden] nos painéis inativos |
visibility:hidden |
com display:none a dobra muda de altura entre abas (537 contra 517 a 1440) e a fileira dança debaixo do cursor |
opacity:0 para poder animar a saída |
visibility + fusão só na foto |
bloco invisível e tabulável é armadilha; visibility sai da árvore de acessibilidade e da ordem de foco |
loading="lazy" nas quatro fotos |
todas eager |
imagem lazy dentro de conteúdo escondido não é buscada, e a primeira fusão atravessaria para um vazio |
roving tabindex escrito no markup |
escrito pelo script | no HTML ele tiraria três das quatro escolhas da ordem de tabulação de quem está sem JavaScript |
<button> nas escolhas |
<a href="#id"> com role="tab" |
é o href que faz a rota sem script existir; o role e o preventDefault fazem o resto |
| legenda em tarja abaixo da foto | dentro da imagem, ancorada no alto | contrato §6, com a máscara do véu invertida para acompanhar |
color-mix de --fg na superfície do card |
color-mix de --light |
misturar a tinta do escopo escurece o card no claro e derruba o realce a 4,25:1 |
| ícone de biblioteca de terceiro | doze SVG desenhados aqui | contrato §11 — ícone proprietário não atravessa |
| recopiar o selo circular | compor efeitos/selo-circular |
o desenho e a volta vivem lá; aqui só o invólucro que ancora |
| troca automática, como carrossel | só por gesto | aba no relógio rouba a leitura e cobraria o botão de pausa da WCAG 2.2.2 |
Divergências conhecidas vs. produção: nenhuma — a peça nasce aqui.
Calibragem no primeiro uso. Espere ajustar três coisas: o --vit-pos de cada foto (o corte que o assunto aguenta com a legenda no alto), o comprimento das frases dos destaques (é o que decide a altura da dobra inteira, porque ela é a do maior painel) e — antes de tudo — qual número cada frente vai mostrar, que é conversa com o compliance do cliente e não decisão de design.