Uma frase de manifesto em corpo grande, quebrada na mão em três a cinco linhas, que não entra e acaba: ela nasce apagada — opacidade de repouso baixa, mas ainda legível — e vai acendendo unidade por unidade conforme a dobra atravessa a janela. Rolar de volta apaga, exatamente na mesma curva. É escovado (scrubbed), reversível e preso ao scroll: o gesto não é "apareceu", é "está sendo lido".
Resolve um problema de ritmo, não de informação. Uma frase de manifesto colada numa página é uma parede de tipografia que o olho lê de uma vez e abandona; o mesmo texto acendendo no compasso da rolagem obriga o olho a andar linha a linha, e é isso que faz a frase parecer dita e não impressa. É uma primitiva de efeito, como efeitos/inclinacao-3d: o que se cataloga é a frase e o motor que a acende, não a dobra em volta.
Compliance
Manifesto é o lugar mais fácil de escorregar para promessa, porque o registro do gênero convida a isso: frase curta, corpo grande, tom de afirmação. Fale de método, estrutura e postura — nunca de resultado. "Patrimônio não se defende no susto" e "em política escrita" são afirmações sobre como a casa trabalha; "seu dinheiro rendendo mais" e "acima do mercado" são promessa de rentabilidade, e não entram aqui de nenhuma forma.
Esta peça não recebe número. Não é limitação de layout: número apurado pede fonte e data, número ilustrativo pede a ressalva .nota do core, e a peça não tem onde pôr uma sem deixar de ser um manifesto — a ressalva embaixo de uma frase de 50px vira uma segunda peça. Copy com cifra pertence a prova/numero-gigante ou a dados/grade-indicadores, que já trazem o aparato.
Sem marca real de terceiro, pelo mesmo motivo de sempre: o manifesto fala da casa, e citar banco ou corretora no corpo dele é comparação implícita.
Quando usar
- Uma frase de manifesto de 3 a 5 linhas, no meio da página, entre duas dobras de peso — identidade, posicionamento, a tese que a página inteira sustenta.
- A dobra tem rolagem antes e depois. O percurso do acendimento é
(start − end)da janela mais a altura do próprio bloco: medido em 1440×900, com o bloco em 432px, dá 648px de rolagem. Sem esse percurso disponível a frase nasce acesa e a peça não faz nada. - Frase curta (4 a 12 palavras) em que a cascata por linha seria um fade só: aí vale a variação por palavra.
- Um palco alto e pinado (
position:stickydentro de um bloco de 300vh) em que a frase fica grudada enquanto a seção rola. A janela de cálculo já cobre o caso sem parafuso novo — ver Variações.
Quando não usar
- A frase precisa ser lida na primeira olhada (hero, chamada acima da linha de corte). No repouso a peça está a 46% de opacidade, e isso é decisão: ela existe para ser lida enquanto se rola. Acima da linha de corte ninguém rolou ainda.
- Você quer revelar a frase e seguir. Aí o gancho é o
data-splitdo core: uma linha de HTML, sem componente nenhum, revela palavra a palavra na entrada e termina. É o irmão mais próximo, e ele ganha sempre que o efeito é de entrada e não de leitura — se a pessoa rolar de volta e você não se importar com o que acontece, você quer odata-split. - Você quer sincronizar texto com outra coisa (uma foto que troca, um trilho que preenche, um índice que marca onde está). Isso é
conteudo/rolagem-sincronizada, e ele ganha por uma diferença de natureza: lá o scroll escolhe um entre N (qual passo está sendo lido) e a peça tem estado; aqui o scroll é uma fração contínua e a peça não tem estado nenhum. Quem precisa de painel, legenda e trilho não deve reconstruí-los em cima desta frase. - A frase leva imagem dentro da linha. Isso é
conteudo/manifesto-com-imagens, que é a mesma tipografia de manifesto da casa com um retângulo de foto ocupando o lugar de uma palavra. Os dois não se combinam bem: lá a imagem é a surpresa da linha, aqui a surpresa é a linha acender. - Mais de duas frases na mesma página. Na terceira o recurso deixa de ser leitura e vira maneirismo.
- Copy com número. Ver Compliance.
Padrão editorial
A tabela descreve a frase, não o palco: a <section> do markup.html é contexto e não faz parte do que se cola.
| Slot | Regra |
|---|---|
Linha (.dts-ln) |
3 a 5 linhas, 18 a 52 caracteres cada. A quebra é decidida aqui, não pelo navegador — cada linha é uma caixa e termina exatamente na palavra escrita. |
Realce (em) |
Opcional, um por frase, dentro de uma linha. 1 a 3 palavras. Realce o verbo ou o advérbio que carrega a tese ("antes", "escrita"), não o substantivo óbvio. |
Palavra (.dts-w) |
Só na variação por palavra, e aí ela substitui a linha como unidade. 4 a 12 palavras. |
O que mantém a peça coesa é o comprimento parecido entre as linhas: a variação de largura entre elas é o desenho do bloco, e uma linha com o dobro da vizinha o desmancha. E o paralelismo de construção — as quatro linhas do exemplo são uma negação seguida de três complementos da mesma oração, o que é o que permite ler a frase inteira depois de ela ter sido lida em pedaços.
O realce nunca atravessa duas linhas: cada linha é uma unidade de opacidade, e um <em> partido acenderia pela metade.
Especificação dos assets
Nenhum. Sem imagem, sem ícone, sem fonte própria: a peça é a .display-xl do core na face do tema, e o efeito é opacidade calculada em CSS.
Como usar
<link rel="stylesheet" href="componentes/efeitos/destaque-texto-scroll/estilo.css">
<script src="componentes/efeitos/destaque-texto-scroll/script.js"></script>
O bloco vai dentro do .wrap da dobra que você já tem — a peça não controla coluna nenhuma (contrato §4):
<div class="dts" data-dts>
<p class="dts-txt display-xl" data-rv>
<span class="dts-ln">Primeira linha da frase.</span>
<span class="dts-ln">Segunda linha, com o realce <em>aqui</em>.</span>
<span class="dts-ln">Terceira linha.</span>
</p>
</div>
Não há nada a inicializar, nenhuma opção a passar e nenhum índice a escrever à mão: o script numera as unidades sozinho, na carga.
Ganchos
| Gancho | Papel |
|---|---|
[data-dts] |
A raiz da peça. É dela que o script lê a posição, e nela que escreve --dts-p e --dts-n. |
.dts-ln |
A unidade linha. O padrão. |
.dts-w |
A unidade palavra. Existindo .dts-w na raiz, ela vence: as linhas viram invólucro. |
--dts-p |
Progresso da dobra, de 0 a 1. Escrito pelo script uma vez por quadro. Estado interno — não se ajusta à mão. |
--dts-n / --dts-i |
Quantas unidades existem, e o índice de cada uma. Escritos na carga. Estado interno. |
--dts-off |
Opacidade da frase apagada. Padrão .46. Tem piso medido — ver Parafusos. |
--dts-k |
Quantas fatias de progresso cada unidade leva para acender. Padrão 2. |
--dts-start / --dts-end |
A janela de cálculo, em % da altura da janela. Padrão 82 e 58. |
Parafusos
| Parafuso | Padrão | O que muda |
|---|---|---|
--dts-off |
.46 |
A opacidade do repouso. Abaixo de .42 a frase perde o AA; abaixo de .46 o realce perde. Ver a tabela de contraste em Gotchas. Subir é sempre seguro, e a partir de .6 o efeito começa a desaparecer. |
--dts-k |
2 |
1 = estritamente sequencial, lê como contador. 2 = a unidade seguinte começa na metade do caminho da anterior, e é isso que faz a leitura parecer uma onda. Acima de 3 vira um fade só. |
--dts-start / --dts-end |
82 / 58 |
Onde o topo do bloco está quando o progresso é 0, e onde a base está quando é 1. 92/20 estica o percurso para quase uma tela inteira. start tem de ser maior que end — se não for, o script cai no par padrão em vez de dividir por um percurso errado. |
--display-xl / --display-xl-lh |
clamp(25px,3.4vw,50px) / 1.26 |
Os dois parafusos da primitiva do core. O corpo é calibrado pela fração da coluna que a linha ocupa, não pelo número: trocar a display do tema muda a conta. |
Variações
- Por palavra — troque as
.dts-lnpor.dts-w, uma por palavra. O script passa a usá-las como unidades sozinho, sem opção a ligar. Para frase curta: numa frase de quatro linhas a cascata por palavra vira ruído, porque o olho lê a linha inteira antes de a terceira palavra acender. - Dentro de seção pinada — o
.dtsdentro de um bloco alto composition:sticky. Medido: com a raiz em 2700px e a frase grudada emtop:25vh, o progresso se espalhou linearmente pelo palco inteiro (p= 0,07 · 0,28 · 0,48 · 0,68 · 0,89 · 1,00 ao longo da seção), sem nenhum parafuso trocado. Marcadaexperimentalporque o que se exercitou foi a conta, num palco montado por script — ainda não há um markup desses catalogado. - No tom claro — troque
.sec-darkpor.sec-paperna seção que hospeda. Nenhuma linha de CSS muda: a tinta é--fgdo escopo e o realce lê--accent-ink, que a.sec-paperjá redefine. Confira o--dts-off: a tabela de contraste de Gotchas foi medida no escuro. - Acendimento seco —
--dts-k:1. Serve a lista de três itens curtos, não a manifesto. - Janela mais longa —
--dts-start:92e--dts-end:20. Para a frase de uma linha só, que com a janela padrão acende rápido demais. - Duas frases na mesma página — dois blocos
[data-dts], cada um com as próprias unidades. O script parte de cada raiz e cada frase mede a si mesma. Duas é o teto.
Gotchas
-
data-rvnunca numa unidade — é a armadilha doinclinacao-3d, trocandotransformporopacity. O core termina o reveal com[data-rv].in{opacity:1}, seletor de especificidade (0,2,0). A regra que escova é.dts-ln/.dts-w, que vale (0,1,0). Se odata-rvmorasse numa unidade, oopacity:1da entrada venceria a cascata para sempre assim que a dobra entrasse — o script continuaria escrevendo--dts-pcertinho, o inspetor mostraria tudo no lugar, e o efeito simplesmente não existiria. Por isso odata-rvmora no.dts-txt, o invólucro, e as unidades por dentro não levam nenhum. É a mesma separação que oefeitos/inclinacao-3dfaz com o.tilt-card-slot; lá a propriedade em disputa eratransform, aqui éopacity. -
O
[data-dts]fica num nó semdata-rv, e não é capricho. É dele que o script lêgetBoundingClientRect()a cada quadro, e[data-rv]em repouso carregatranslateY(22px). Medir num nó deslocado faria o progresso nascer 22px atrasado e se corrigir sozinho durante o segundo da entrada — um salto pequeno, mas gratuito. A raiz mede; o filho revela. -
Não escreva a marca de fim de comentário dentro de um comentário — este componente pagou por isso. A primeira versão do cabeçalho do
markup.htmlcitava o delimitadorCOMPONENTE — INÍCIO/FIMcom a marca de fechamento inteira, entre crases. O navegador fechou o comentário ali: o resto do cabeçalho virou markup, e o<em>que o texto mencionava três parágrafos abaixo virou um elemento de verdade que engoliu a página. Medido em 390px de largura: a página foi a 885px de rolagem horizontal, com um único<em>de 885px e 40 retângulos de linha — e oconformidade.pypassou limpo, porque o CSS e o JSON estavam certos. É primo do caso 6 do MANUAL §6, onde o exemplo de markup em span inline vazava caminho resolvível na ficha: ali a citação vaza um caminho, aqui vaza o fim do comentário. Escreva o delimitador por extenso, sem os sinais. -
O estado apagado é um ESTADO, não um quadro de transição — e por isso ele fecha contraste sozinho. Quem parou de rolar fica olhando para a frase a 46%. Medido no tema liberta-wealth, escopo
.sec-dark(--fg#f2f0ec e--accent#c6ceaa sobre #0a0a0a), compondo a tinta contra o fundo:
| opacidade | texto (--fg) |
realce (--accent-ink) |
|---|---|---|
.38 (o valor da referência) |
3,22:1 | 2,57:1 |
.42 |
3,70:1 | 2,90:1 |
.46 (o daqui) |
4,23:1 | 3,26:1 |
.50 |
4,81:1 | 3,66:1 |
1 (aceso) |
17,39:1 | 12,08:1 |
O mínimo para texto grande é 3:1 — e esta frase é sempre texto grande (medido no palco: 48,96px em peso 700; o piso do clamp é 25px, e o limite da regra é 18,66px em negrito). A referência repousa em .38, onde o texto passa raspando e o realce não passa — e o realce é justamente o que a peça quer que se leia. .46 é o menor valor que fecha os dois.
-
A janela de cálculo é
(start − end)da janela MAIS a altura do bloco, e o primeiro par de valores tentado estava errado. Com--dts-end:34, a última linha da frase de quatro linhas só fechava com o topo do bloco em −126px: a primeira linha já tinha saído pela borda de cima quando a última acendeu, e a frase inteira acesa nunca chegava a existir na tela. Com82/58o progresso fecha com o bloco em 90..522 da janela — inteiro dentro, na metade de cima. Medido no mesmo palco: com o bloco centrado verticalmente o progresso está em 0,78 e a última linha em 70% do caminho, ou seja, ainda há o que ver acendendo depois de a frase estar toda na tela. O número que decide é a altura do bloco, então frase muito mais alta que a do exemplo pede rever o--dts-end. -
Sem
transition, e é decisão. O valor é a posição do scroll. Qualquer amortecimento transformaria "escovado" em "atrasado", e numa rolagem de volta o rastro ficaria atrás da mão. A suavidade vem do--dts-k, que sobrepõe as unidades, não de uma curva no tempo. -
A conta em CSS custa dez vezes mais por quadro que escrever a opacidade em JavaScript — e fica assim mesmo. Medido em 1440×900, Chrome headless, 400 iterações de escrever + forçar recálculo: 0,12–0,14 ms/quadro para as quatro linhas com
--dts-pna raiz, contra 0,011–0,016 ms escrevendostyle.opacityem cada uma; com 27 palavras, 0,45 ms contra 0,022 ms. A razão é conhecida: escrever uma custom property na raiz invalida o estilo de toda a subárvore que a herda. Fica assim porque o pior caso medido é 2,7% de um quadro de 16,7 ms — e esse pior caso é um manifesto inteiro partido em 27 palavras, que é justamente o markup que esta ficha desaconselha; no markup padrão são 0,8%. Em troca, a curva inteira (oclamp, o piso, a sobreposição) mora junto da regra que ela limita, e sem script a conta cai inteira em vez de deixar a frase apagada. Se um dia a peça for parar numa página com dezenas de unidades, é este número que decide a troca — não o gosto. -
Sem script a frase fica ACESA, por dois caminhos independentes. O declarado é
@media (scripting: none), a mesma consulta que ocore/motion.cssusa para devolver os[data-rv]. O outro é acidente da cascata, e foi medido de propósito para saber que ele também aponta para o lado certo: sem--dts-ie--dts-n, ovar()sem fallback torna a declaração inválida no tempo de valor computado, eopacity— que não é herdada — volta ao valor inicial, 1. Medido apagando--dts-ide uma unidade com a página carregada: o computado dela foi de 0,46 para 1. Isso cobre o caso que a media query não cobre — JavaScript ligado e o script bloqueado, com 404 ou por CSP. -
Palavra e linha nunca convivem como unidade. Se houver
.dts-wna raiz, elas são as unidades e as.dts-lnviram invólucro; a linha que sobra sem--dts-icomputa opacidade 1, que é o neutro certo. Um markup que quisesse os dois conjuntos escovando ao mesmo tempo daria duas ondas desencontradas — por isso o script escolhe um, e não soma. -
Em tela estreita uma "linha" pode virar duas linhas visuais, e a unidade continua sendo o
<span>inteiro. Medido em 390×844: as quatro.dts-lndo exemplo ocupam 228px em corpo de 25px, ou seja, sete caixas de linha. A cascata continua legível porque a unidade é a frase e não a caixa; mas uma.dts-lnque vira três linhas visuais é sinal de que a copy passou do teto de 52 caracteres. -
Uma unidade só não escova nada. O script sai sem erro quando encontra menos de duas, sem escrever
--dts-i— e a frase fica acesa, que é o estado certo para uma linha sozinha. Frase de uma linha que precisa de escovado pede a variação por palavra.
Movimento reduzido / toque
prefers-reduced-motion: reduce: o script sai no guard, antes de escrever qualquer coisa — não numera as unidades, não assina o laço de rolagem do core e não observa nada. Medido com --reduce em 1440×900, com a dobra na tela: --dts-p e --dts-n saem vazios, o --dts-i de cada linha sai vazio, e a opacidade computada das quatro linhas é 1. O @media(prefers-reduced-motion:reduce) do estilo.css declara o mesmo por escrito, para a garantia não depender de um efeito colateral da cascata. Não existe versão "acende sem rolar": o acendimento é a rolagem, e simular um relógio no lugar dela seria exatamente o movimento automático que a preferência pediu para desligar.
Toque: nada muda, e não há nada a mudar — o gesto é a rolagem, que existe igual em toque e em mouse. Não há hover em lugar nenhum desta peça. Medido em 390×844 com toque: as quatro unidades numeradas, --dts-p acompanhando, overflow-x ok.
?motion=on funciona, porque o guard passa pelo LIB.reduce do core — mas não há o que ele mostre a mais: com a preferência ligada a peça não anima sozinha, ela só deixa de reagir ao scroll.
Histórico
| Projeto | Onde | Data | Diferenças |
|---|---|---|---|
referência: skinboost-design-review.vercel.app |
a dobra #manifesto — as linhas saindo de opacity .38 para 1 conforme a seção passa |
2026-09-21 | reescrito do zero: sem GSAP/ScrollTrigger, sem three.js, sem fonte de terceiro; repouso subiu de .38 para .46 por contraste medido; realce passou a ler --accent-ink |
Origem. Clone de referência (contrato §11): o que veio do original foi a ideia da interação — a frase de manifesto que acende conforme a seção passa, e volta a apagar —, a proporção e o timing. Nenhuma linha de código foi consultada ou copiada. Registro consolidado em projetos/referencias-externas.md, seção Clones.
Decisões de tradução.
O original escova a opacidade com GSAP ScrollTrigger em scrub, que é literalmente o caso em que o §1 do contrato autorizaria GSAP. Ele não atravessou mesmo assim, e a razão não é purismo: a peça não precisa de timeline encadeada, precisa de uma fração — e o core já serve o laço de scroll da página inteira em LIB.onScroll, um requestAnimationFrame para todos os componentes juntos. Uma lib de 70kB para produzir um número entre 0 e 1 seria o pior negócio da biblioteca.
A divisão do trabalho mudou de forma. No original o script escreve a opacidade de cada linha a cada quadro. Aqui ele escreve uma custom property na raiz (--dts-p) e cada unidade resolve a própria opacidade em CSS, a partir do índice que recebeu na carga. O número não favorece a escolha (ver Gotchas), e é por isso que ele está escrito na ficha e no cabeçalho do script: quem for trocar um dia tem o custo medido na mão, não uma preferência.
A correção que mais importa é de acessibilidade, e ela só apareceu porque foi medida: o repouso em .38 do original dá 2,57:1 no realce em cor de marca sobre o escuro da casa, contra um mínimo de 3:1 para texto grande. O padrão daqui é .46, e a ficha registra o piso abaixo do qual o parafuso volta a quebrar. Entraram também três coisas que o original não declara: @media (scripting: none) e @media(prefers-reduced-motion:reduce) deixando a frase inteira acesa, e o guard no script que nem assina o laço quando a preferência está ligada.
A janela de cálculo foi reescrita para servir aos dois casos que o contrato deste componente exige — bloco baixo e seção pinada alta. O ScrollTrigger resolve isso com start/end em string ("top 80%", "bottom 20%"); aqui é uma divisão em que o denominador inclui a altura do próprio bloco, o que faz o percurso crescer junto com a peça sem nenhum parafuso novo. O primeiro par de valores tentado (76/34) estava errado, e só o número mostrou — ver Gotchas.
Normalizações aplicadas na extração: prefixo dts- em toda classe; .display-xl do core no lugar da tipografia própria; --accent-ink no lugar de --accent no realce; data-rv do core como entrada, no invólucro; LIB.onScroll no lugar de listener próprio; copy de assessoria na voz da Liberta no lugar da copy de skincare.
Divergências conhecidas vs. a referência: o repouso é mais claro (.46 contra .38), e a granularidade por palavra não existe no original — ela entrou porque a frase curta é um caso real da casa e a cascata por linha a resolveria mal.