A dobra "como funciona" que anda sozinha no tempo. À esquerda, a lista de etapas: número, título e a descrição, que só fica aberta na etapa ativa; embaixo de cada uma, um filete que enche no tempo da etapa e diz quanto falta. À direita, o painel que troca junto — foto ou peça de interface —, em fusão com desfoque. Acima, três discos de reprodução: anterior, pausar, próxima. A cada seis segundos a dobra passa para a etapa seguinte, e quando alguém escolhe uma etapa o rodízio desliga em definitivo — quem escolheu não quer ser trocado.
Resolve o processo que precisa ser visto acontecer. Uma lista de quatro etapas empilhadas é honesta e não chama ninguém; um processo que se apresenta sozinho, com foto trocando e um filete contando o tempo, é lido por quem só ia passar o olho. A troca no tempo, e não na rolagem, é o que separa esta peça das duas irmãs: aqui a dobra se apresenta mesmo para quem parou de rolar, e quem quiser dirigir assume o controle a qualquer momento.
Compliance
- As etapas descrevem processo, nunca resultado. O que é feito, por quem, com que periodicidade. Nenhuma etapa desta peça fala de rentabilidade, retorno, desempenho ou "melhor investimento" — e isso é regra do bloco, não escolha de redação. Uma dobra de "como funciona" é exatamente onde a promessa de resultado costuma entrar por descuido, disfarçada de etapa ("acompanhamos até sua carteira render mais").
- Prazo e periodicidade são de contrato. O markup fala em "a cada semestre" porque é o que a
.notado fim declara como acordado em contrato. Cravar prazo numérico sem essa ressalva transforma uma descrição de método em compromisso. - Os painéis são fotos ilustrativas, e a peça diz isso. A
.notamora dentro da dobra, onde a pessoa está olhando a foto, e não no rodapé da página. Se o painel virar captura de tela de relatório, a ressalva precisa mudar junto — número em captura de tela é número apurado, e aí ele tem fonte e data. - Nome de instituição só fictício. Os nomes que a casa usa são Meridiano, Altamira, Northbay e Quadrant.
Quando usar
- A dobra "como funciona" / "nosso método" de uma home ou de uma página de serviço, com três a cinco etapas.
- Quando cada etapa tem uma imagem própria que vale a pena olhar — foto editorial, peça de interface, documento. Sem material visual por etapa, metade da dobra fica vazia.
- Quando a página já tem uma dobra presa à rolagem e uma segunda cansaria: esta anda no tempo e não disputa o scroll.
- Quando o processo é linear e a ordem é a informação.
Quando não usar
- O irmão mais próximo é o
listas/linha-do-tempo-etapas, e ele ganha quase sempre que a dobra é para LER. Lá as cinco etapas ficam abertas ao mesmo tempo, empilhadas numa coluna só, com prazo em pílula por etapa e um filete que se preenche com a rolagem. Quem está comparando etapas, conferindo prazos ou lendo com atenção precisa ver tudo de uma vez — e aqui só uma descrição fica aberta por vez, de modo que comparar exige voltar. A regra prática: dobra para ler → linha do tempo; dobra para impressionar → etapas em abas. Ele também é a escolha certa quando não há imagem por etapa, quando o texto de cada etapa passa de 60 palavras, e quando as etapas têm prazos que a pessoa vai querer somar. conteudo/rolagem-sincronizadaganha quando a dobra pode ocupar várias telas. Lá a troca do painel é dirigida pela rolagem, o texto de cada passo é longo e o painel fica pinado — é o formato de fôlego. Aqui a dobra inteira cabe em uma tela e não pede rolagem nenhuma. Se a página já tem uma dobra presa ao scroll, pôr duas é fazer as duas competirem pelo mesmo gesto.- Conteúdo que a pessoa precisa comparar lado a lado. Use
listas/tabela-comparativaoulistas/paineis-empilhados. - Mais de cinco etapas. A coluna da esquerda fica mais alta que o palco e a dobra desequilibra; e cinco etapas de seis segundos são trinta segundos de rodízio, que é mais do que alguém espera.
- Duas etapas. Não lê como processo: lê como um seletor de duas opções. Use
listas/leque-paineis. - Acima da dobra. É seção de miolo. No topo, uma peça que troca sozinha compete com a headline — para hero em rodízio existe
hero/slideshow-cinematico, que é de onde esta peça herda o motor. - Ao lado de um formulário na mesma tela. Movimento automático ao lado de um campo de texto rouba a atenção do campo.
Padrão editorial
| Slot | Regra |
|---|---|
| Etapas | Quatro é o ponto de equilíbrio. Três é o piso (com duas a lista vira seletor); cinco é o teto. Acima disso a coluna passa a altura do palco. |
| Olho | 1 a 3 palavras. |
| Título da seção | 3 a 8 palavras, com uma expressão em <em>. Diga o recorte do processo — "como funciona" o olho já disse. |
| Apoio | 60 a 180 caracteres. É o lugar de avisar que a dobra anda sozinha ("pare quando quiser ler com calma"): quem chega no meio de uma troca precisa saber que pode parar. |
| Número da etapa | Dois dígitos (01, 02). É decoração e vai em aria-hidden — ver o gotcha. |
| Título da etapa | 1 a 4 palavras, substantivo, não frase. É o nome acessível da aba: "Política de investimentos" ganha de "Escrevemos a sua política". Até ~26 caracteres, senão quebra em duas linhas em 390px. |
| Descrição da etapa | 30 a 60 palavras, e de tamanho comparável entre as etapas (±10 palavras). Descreve o processo, nunca o resultado. |
| Legenda do painel | 30 a 90 caracteres, uma linha em desktop. Vai dentro da imagem, ancorada embaixo. Acima de 90 caracteres o véu cresce e come a foto. |
| Ressalva | 60 a 260 caracteres, na primitiva .nota. Obrigatória enquanto os painéis forem ilustrativos. |
O que mantém a dobra coesa é o paralelismo das descrições. A coluna da esquerda muda de altura a cada troca — a etapa que abre empurra as de baixo. Com quatro descrições do mesmo tamanho o movimento é um respiro; com uma de três linhas ao lado de uma de sete, a lista inteira pula a cada seis segundos e o olho perde o lugar. Medido no padrão: descrição aberta de 103,9px em 1440 e 146,0px em 390, com a lista fechando em 360px contra os 361,9px do palco — as duas colunas terminam praticamente na mesma linha, e é isso que se está protegendo.
Os títulos das etapas devem ter a mesma forma gramatical: quatro substantivos, ou quatro verbos. Misturar "Diagnóstico" com "Implementamos a carteira" desmancha a régua.
Especificação dos assets
- Formato e dimensão mínima: paisagem, WebP, mínimo 1600 × 900, ideal 2000 × 1125. O palco é uma caixa de
aspect-ratio16/11 (16/10 abaixo de 940px) comobject-fit:cover— a foto é cortada em cima e embaixo, nunca esticada. - Peso alvo: até 160 KB por foto. São três a cinco imagens na mesma dobra. Todas vêm com
loading="lazy": ficam empilhadas na mesma caixa e, quando a dobra se aproxima da tela, o navegador as busca juntas — que é o que se quer, porque a primeira troca acontece seis segundos depois. - Composição: assunto fora do terço inferior, que é onde a legenda e o véu moram. Recorte pensado para 16/11 e conferido em 16/10.
- Direção de arte igual em todas: mesma hora do dia, mesma temperatura de cor. Quatro fotos de bancos diferentes trocando a cada seis segundos denunciam o banco.
- O que não pode ter: texto embutido, logo, marca d'água, e foto clara demais para aguentar a legenda branca — o véu ajuda, não salva.
- Se o painel for peça de interface em vez de foto, a legenda sai (não há foto para ela morar dentro) e o palco passa a receber um bloco montado em HTML, no padrão do
conteudo/trio-cards-ui-simulada.
Como usar
<link rel="stylesheet" href="componentes/listas/etapas-em-abas/estilo.css">
<script src="componentes/listas/etapas-em-abas/script.js"></script>
Cole o markup.html como uma seção do miolo, troque as fotos, os alt, as legendas e o texto das etapas. Precisa do core/tokens.css e do core/base.css. O core/motion.js é quase obrigatório: é dele que vêm o LIB.reduce e o LIB.pausavel, que é quem mostra e comanda o botão de pausa — sem ele o script cai no matchMedia e o botão fica escondido como nasceu, com a dobra inteira funcionando no resto.
Para acrescentar ou tirar uma etapa, mexa sempre nos dois juntos e na mesma ordem: o <button role="tab"> na lista e a <figure role="tabpanel"> no palco. O script casa os dois por índice e ignora a sobra.
Ganchos
| Gancho | Papel |
|---|---|
[data-eta] |
Raiz. O script parte daqui, nunca do documento — a dobra pode se repetir na página. |
[data-eta-lista] |
O role="tablist". É ele que pausa no ponteiro e quem recebe as setas do teclado. |
[data-eta-grade] |
A grade de duas colunas. O foco dentro dela pausa; foco nos discos, não — ver o gotcha do focusin. |
[data-eta-aba] |
Cada <button role="tab">. |
[data-eta-rot] |
O título da etapa. É de onde sai o texto da região viva. |
[data-eta-painel] |
Cada <figure role="tabpanel">. |
[data-eta-ant] / [data-eta-prox] |
Os discos de anterior e próxima. |
[data-eta-toggle] |
O botão de pausa. Veste .btn-disc .btn-play do core, nasce [hidden], e quem o mostra é o LIB.pausavel. |
[data-eta-vivo] |
A região aria-live="polite" em .visually-hidden. |
.is-live |
Na raiz, escrito pelo script: fecha as descrições e faz os três discos existirem. |
.is-paused |
Na raiz: relógio parado. Congela o filete de progresso. |
.is-on |
No painel ativo. |
Parafusos
| Variável | Padrão | O que faz |
|---|---|---|
--eta-dur |
6s |
O relógio da dobra. O filete e o setTimeout leem este mesmo valor — o script o pega por getComputedStyle na raiz. Nunca há dois números para manter em sincronia. |
--eta-swap |
.7s |
Fusão entre um painel e o seguinte. |
--eta-open |
.44s |
Abertura e fecho da descrição. Mais curta que a fusão de propósito. |
--eta-blur |
14px |
Desfoque da fusão do painel. 8px abaixo de 720px. |
--eta-rot |
clamp(17px,1.5vw,21px) |
Corpo do título de cada etapa. |
--eta-palco |
16 / 11 |
Proporção do palco. 16 / 10 abaixo de 940px. |
--nota-max |
78ch |
Parafuso da primitiva .nota, escrito na .eta-nota. |
Variações
- três ou cinco etapas — acrescente ou tire o par aba + painel.
- ritmo de leitura longa —
--eta-dur:8spara descrições de 60 a 70 palavras. - no tom claro — troque
.sec-deeppor.sec-paper. Nenhuma linha de CSS muda: o número da etapa ativa lê--accent-ink, que o escopo claro resolve sozinho. - painel de interface no lugar da foto — o palco já é caixa de
aspect-ratiocomoverflow:hidden; o que sai é a legenda. - duas dobras na mesma página — só se estiverem longe uma da outra. Os ids são gerados por instância e cada relógio é independente, inclusive a pausa.
Gotchas
- O filete de progresso é CSS, e o reinício dele sai de graça. A regra que anima casa com
.eta-aba[aria-selected="true"] .eta-fill, e só com ela. Trocar de etapa tira a regra de uma aba e a põe na outra — e uma animação que passa a casar começa do zero. Não há uma linha de script escrevendo largura por quadro, e não há o que reiniciar à mão. Medido ao longo de um ciclo: aos 6,4s a animação da aba nova estava em 725ms, e não em 6400ms. - A pausa precisa vencer a shorthand
animationem especificidade.animation:etaFill …é shorthand e zera todas as longhands dela, inclusiveanimation-play-state. A regra que anima vale (0,5,0); a de pausa soma.is-pausede vale (0,6,0), exatamente por isso. Escrita com menos, o botão de pausa "não funcionaria" — sem erro nenhum no console. Mesma armadilha registrada nohero/slideshow-cinematico. grid-template-rows:0frsozinho NÃO fecha nada, e isso custou duas medições. Por especificação,0fréminmax(auto,0fr), e o mínimo automático de um item de grade é omin-contentdele: a linha se recusa a descer abaixo da altura do texto. Medido: as quatro descrições em 103,938px, fechadas e abertas — a transição rodava, e nada acontecia.min-height:0no item baixou para 10px, que é opadding-topdo filho (padding continua contando nomin-content). Só escrever o mínimo na pista, comminmax(0,0fr)/minmax(0,1fr), fechou de verdade: 0px fechada, 103,938px aberta. As duas pontas com o mesmo mínimo também é o que permite a transição interpolar. Quem "simplificar" de volta para0fr/1frdevolve o defeito, e ele é silencioso.- O foco é medido na GRADE, e não na seção inteira — senão o botão de pausa faz o contrário do que diz. Clicar num
<button>dá foco a ele, efocusindispara no mousedown, antes doclick. Com o foco medido na seção, apertar o botão de pausa acionava primeiro ofocusin(que pausa e escreve "pausado" no botão) e só depois o clique do próprioLIB.pausavel, que alterna a partir do estado recém-escrito — e despausava. Nenhum erro no console, nenhum alerta de conformidade: só um botão que não funciona. Medir o foco onde alguém está lendo (a lista e o painel) resolve o defeito e é a leitura certa da regra — os controles de reprodução não são conteúdo. - Qualquer escolha explícita desliga o rodízio, e o caminho de volta é o botão. Clique na aba, seta do teclado, disco de anterior ou próxima: o relógio para e não volta sozinho. Uma lista que continua trocando depois de alguém ter escolhido é uma lista que ninguém consegue ler (precedente:
conteudo/trio-cards-ui-simulada, que trava no primeiro clique). A diferença aqui é que a peça tem um botão de reprodução, cujo rótulo já diz "Retomar as etapas" — então a saída não é um controle morto, é o controle que existe para isso. Medido: clique na aba 3 →is-pausedna hora, mesma etapa nove segundos depois; clique no botão → volta a andar e troca 6s depois. - O ícone do botão diz o estado EFETIVO, e não só o que foi apertado. Toda pausa — do botão, de uma escolha, da aba do navegador oculta, da dobra fora da tela, do ponteiro sobre a lista, do foco na grade — passa pelo mesmo
ctl.trocar(). Um botão mostrando "pausar" enquanto nada anda afirma um estado falso. O preço assumido é que o ícone vira ▶ enquanto o ponteiro está sobre a lista; é a verdade, e informa. A travarefletindono script existe para que essa escrita programática não seja lida de volta como intenção da pessoa — sem ela, tirar o ponteiro da lista apagaria um pause de verdade. - O relógio guarda o que FALTA, não o que já passou. Cada pausa desconta o decorrido de
restante, e a retomada agenda só o restante. Medido: pausado aos 2251ms, o filete congela em 2255ms e continua nos mesmos 2255ms nove segundos depois; ao retomar, a etapa troca 3,7s depois, e não 6s. Sem isso, um hover de meio segundo devolveria seis segundos cheios aosetTimeoutenquanto o filete continuaria de onde parou, e os dois se descolariam a cada pausa. - Aba em segundo plano pausa de verdade.
setTimeoutem aba oculta não para: o navegador só o afrouxa para no mínimo 1s. Sem tratarvisibilitychange, quem volta depois de um minuto encontra a dobra dez etapas adiante. Medido: ao esconder a aba,.is-pausedentra no mesmo instante, a etapa continua a mesma depois de 8s, e ao voltar o relógio retoma. - A descrição mora DENTRO do
<button role="tab">, e não é descuido. Umrole="tablist"só pode conterrole="tab": não há onde pôr um parágrafo irmão da aba sem quebrar a árvore que o leitor de tela recebe. A consequência precisa ser tratada — sem isso, o nome acessível de cada aba inativa carregaria as quarenta e cinco palavras da descrição dela. Quem resolve é ovisibility:hiddenatrasado: a descrição fechada sai da árvore de acessibilidade, o nome da aba inativa é só o título, e o da ativa traz a descrição junto. Medido:visibilityvisiblena ativa ehiddennas outras três. - O número da etapa é
aria-hidden, e aqui isso é permitido. O MANUAL §6 registra quelist-style:nonecusta a semântica de lista no Safari, e que onde a ordem é a informação é obrigatório devolvê-la — porrole="list"ou por número audível. Esta peça não é<ol>: é umtablist, e o própriorole="tab"entrega posição e total ("guia 2 de 4") sem que ninguém escreva nada. É a terceira saída, e ela só existe aqui. Se alguém trocar o tablist por uma lista, o número tem de perder oaria-hiddenno mesmo commit. - Só a troca automática é anunciada. A escolhida por alguém já é narrada pela própria aba, que recebe o foco e o
aria-selected; anunciar as duas faria o leitor de tela dizer cada etapa duas vezes. Medido: a região viva escreveu "Etapa 2 de 4: Política de investimentos" e "Etapa 3 de 4: Implementação" nas trocas automáticas, e ficou vazia na navegação por teclado. - A pausa por ponteiro é só na lista. Na seção inteira seria pior que não ter: a dobra ocupa a largura da coluna, e um ponteiro parado em qualquer canto congelaria o rodízio para sempre — sem que ninguém entendesse por quê. Sobre os discos também não pausa: quem está indo apertar o botão não deve ver o ícone mudar embaixo do cursor.
data-rvvai no invólucro, sempre. O filete se move portransform:scaleX(), e o core termina todo reveal em[data-rv].in{transform:none}— seletor (0,2,0), que vence qualquer classe do componente no mesmo nó. Aqui odata-rvmora em.eta-cole em.eta-palco, que são ancestrais, e ancestral não é problema (MANUAL §5). Quem "limpar" pondo odata-rvna própria aba apaga o filete em silêncio assim que a dobra entra.- Os
<img>do palco não trazemwidth/height, de propósito. Atributo de dimensão venceaspect-ratioe o palco desandaria — é a armadilha 2 do MANUAL §6, medida nomidia/faixa-videos. Aqui quem manda na caixa é o--eta-palco, e a imagem preenche comobject-fit:cover. - A legenda precisa do
z-index:2para o véu funcionar. Não é para subir na pilha: é o que faz a legenda virar contexto de empilhamento, para que oz-index:-1do véu fique atrás do texto e ainda assim acima da foto. Sem ele o véu desce para a pilha do painel (que já é contexto próprio, porque temfilter) e some debaixo do<img>. Precedente:midia/grade-cases-foto. - Sem JavaScript a dobra abre inteira, e nenhum controle aparece. As quatro descrições ficam visíveis (o fecho é do
.is-live), o primeiro painel está no markup com.is-on, e os três discos ficam emdisplay:none— controle que não comanda nada é pior que controle nenhum. Medido com o JavaScript desligado: 1270px de altura contra os 960px da dobra viva, que é exatamente o espaço das três descrições a mais, sem overflow horizontal e com o console limpo.
Movimento reduzido / toque
Movimento reduzido desliga o que anda sozinho e não desliga o que a pessoa pede. Medido com prefers-reduced-motion: reduce: o relógio não liga e nada troca em 9 segundos; o LIB.pausavel não é chamado e o botão de pausa fica [hidden] (display:none), porque não há troca automática para pausar; o filete tem animation-name:none e fica em scaleX(0) — ele some, em vez de ficar cheio fingindo que contou; a abertura da descrição perde a transição e acontece na hora. As abas, as setas do teclado e os discos de anterior/próxima continuam funcionando: medido, clique na aba 3 abriu a descrição e trocou o painel, e o disco de próxima levou à aba 4.
O filete aqui some, e não fica cheio como o do hero/slideshow-cinematico. Lá ele é a régua de uma barra de abas e um trecho cheio lê como "você está aqui"; aqui ele é a única peça que se move, e um filete cheio parado embaixo da etapa ativa leria como "esta etapa terminou".
Toque. Não há nada que dependa de hover: o realce de passagem da aba é desligado em @media(hover:none) e a tinta de repouso já é --fg-2, que fecha contraste sozinha. Os três discos são alvos de 42px, a aba inteira é clicável (número, título e descrição), e a pausa por ponteiro simplesmente não existe no toque — o que sobra é o botão de pausa, que é justamente por isso que a WCAG 2.2.2 o exige. Abaixo de 940px o palco sobe para cima da lista: a foto trocando precisa estar na mesma tela de quem está lendo as etapas, senão trocar é o mesmo que não trocar.
Histórico
| Projeto | Onde | Data | Diferenças |
|---|---|---|---|
| referência: skinboost-design-review.vercel.app | dobra #como-funciona |
2026-09-21 | reescrito do zero; ver abaixo |
Origem. Clone de referência (contrato §11): o site de origem foi lido com o olho, não com o inspetor, e nenhuma linha atravessou. O registro consolidado vai em projetos/referencias-externas.md.
Decisões de tradução para o contrato:
- A stack inteira saiu. O original anima com GSAP e ScrollTrigger, desenha ícones da Phosphor e puxa assets de Figma. Aqui é HTML, CSS e JS vanilla dentro de uma IIFE; as setas são SVG inline no markup, as fotos vêm dos placeholders de
showroom/demo/e não há uma lib. - Anda no tempo, e essa é a razão de a peça existir. A biblioteca já tinha a mesma dobra presa à rolagem (
conteudo/rolagem-sincronizada) e a mesma dobra parada (listas/linha-do-tempo-etapas). O que faltava era a que se apresenta sozinha — e uma dobra que se apresenta sozinha precisa, por isso mesmo, de um jeito de ser parada. - Acessibilidade que o original não tinha. Padrão de abas do W3C APG de verdade:
role="tablist"comaria-orientation="vertical",role="tab"comaria-selected,role="tabpanel"amarrado poraria-controls/aria-labelledbycom ids gerados pelo script (id em markup de biblioteca colide na primeira página que repetir a dobra), roving tabindex, as quatro setas maisHome/End,tabindex="0"no painel ativo porque ele não tem nada focável dentro, painel inativo comaria-hiddenmaisinertmaistabindexremovido, e uma regiãoaria-live="polite"que anuncia só a troca automática. - O botão de pausa é a primitiva nova do core.
.btn-disc .btn-playcomLIB.pausavel(WCAG 2.2.2), com a copy dos dois rótulos morando no markup, emdata-rot-pausar/data-rot-tocar, e não dentro do script. - Cor por token, e a distinção que o MANUAL §3 obriga. O número da etapa ativa lê
--accent-ink, porque é texto sobre o fundo do escopo; o filete continua em--accentpuro, porque é traço. O véu da legenda écolor-mixsobre--dark. Zero cor crua fora da máscara em gradiente. - Medida por variável. Duração, fusão, abertura, desfoque, corpo do título e proporção do palco viraram
--eta-*na raiz do componente — e--eta-duré lido pelo script, para que o filete e o relógio nunca tenham dois números. - Copy trocada por completo. O original fala de rotina de skincare com IA. Aqui é o processo de uma assessoria — diagnóstico, política de investimentos escrita, implementação e comitê de revisão —, sem uma palavra sobre rentabilidade ou desempenho, e com a
.notado core declarando que os painéis são ilustrativos.
Normalizações aplicadas na extração: prefixo eta- em toda classe; cores cruas trocadas por token de escopo; ganchos data-eta-* em lugar de seletor de classe no script; data-rv movido para invólucros; legenda posta dentro da imagem com véu mascarado; .nota e .btn-play do core adotadas em lugar de receita local.
Divergências conhecidas vs. a referência: o original tem cinco etapas e uma animação de entrada por ScrollTrigger; aqui são quatro e a entrada é o data-rv do core. O painel do original é um render 3D em three.js — aqui é foto, e a ficha diz qual seria o asset real (foto editorial ou peça de interface, 16/11, até 160 KB).