Uma parede de fotos que não para atrás de uma frase que não se mexe. Quatro colunas verticais de retratos, cada uma uma esteira infinita: as vizinhas correm em sentidos opostos e em velocidades diferentes, o conjunto inteiro está inclinado seis graus e ampliado até sangrar as quatro bordas, e as pontas de cima e de baixo se dissolvem em máscara em vez de cortar. No centro, sobre um véu que escurece só o miolo, ficam parados o olho, a frase e o botão. O olho é puxado pelo movimento periférico, percebe que há vida ali, e volta para o único lugar da dobra que está quieto — que é exatamente onde está o que a página quer dizer.
Resolve o problema da dobra que precisa de presença e não tem uma foto que aguente a tela inteira. Um hero de mídia cheia depende de uma imagem excepcional; quando o material é um banco de oito fotos boas e nenhuma extraordinária, a foto única entrega uma abertura morna. A parede troca a qualidade de uma pela quantidade de todas: nenhuma foto precisa se sustentar sozinha, porque nenhuma fica tempo suficiente para ser julgada. E o movimento contínuo, lento e sem começo nem fim visível, diz "isto está acontecendo" sem prometer nada — que é o registro certo para uma marca que não pode prometer retorno.
Quando usar
- Dobra de convite no meio ou no fim da página: um convite curto, com um botão, sobre movimento. É o uso principal.
- Abertura de uma página secundária (Quem somos, Para você), quando o hero principal do site já usa foto única e repeti-la seria eco.
- Quando existem 6 a 12 fotos de gente de qualidade parecida — família, escritório, encontro, mesa — e nenhuma delas é a foto do ano.
- Quando a dobra precisa de peso visual sem texto longo: cabe uma frase de duas linhas e um apoio de duas, e mais nada.
- Como divisória entre duas dobras escuras, sem texto nenhum (ver Variações).
Quando não usar
- Quando existe uma foto que carrega a página sozinha, com headline longa e régua de números:
hero/full-bleed-media. Lá a foto é o argumento; aqui ela é a textura. - Quando é a última dobra antes do rodapé e o assunto é fechar:
conversao/cta-fechamento-fotofaz esse trabalho com uma foto só e sem custo de animação. Duas dobras de foto em movimento na mesma página cansam. - Quando as pessoas nas fotos têm nome e precisam ser lidas:
midia/carrossel-perfis. Aqui o retrato passa cortado, torto e a 55% de opacidade — ninguém reconhece ninguém, e é de propósito. - Quando cada foto leva a algum lugar:
midia/galeria-scroll-horizontal, em que o card é um<a>de verdade. Um alvo que anda sozinho é um alvo que ninguém acerta — nada aqui é clicável além do botão. - Quando o argumento é quantidade de clientes ou de marcas, e não clima:
prova/marquee-logosouprova/carrossel-depoimentos, que são a mesma receita de esteira no eixo horizontal, com item pequeno e conteúdo real. - Quando o texto tem mais de duas linhas de frase e duas de apoio. Cresceu, saiu do miolo do véu, e o contraste cai de 14:1 para menos de 3:1 sobre uma foto clara. Aí é dobra de texto com foto ao lado —
conteudo/apresentacao-foto-alternada. - Em página que já tem
efeitos/moldura-secaona dobra vizinha: são dois desvanecimentos de borda encostados, e o segundo anula a leitura do primeiro.
Padrão editorial
| Slot | Regra |
|---|---|
Olho (.eyebrow) |
2 a 4 palavras, sem verbo. Diz para quem é a dobra ("Para famílias e empresários"), não o que ela oferece. |
Frase (.fall-title) |
6 a 9 palavras, em duas linhas, com a quebra escrita à mão no <br>. Uma linha só desperdiça o palco; três empurram o apoio para fora do miolo do véu. É afirmação, não chamada: o botão já chama. |
Apoio (.fall-lead) |
90 a 150 caracteres, uma ou duas linhas. Explica o que a frase promete em termos concretos ("entender cada ativo", "deixar o próximo passo organizado"). Acima de 150 vira parágrafo e a dobra perde o silêncio. |
Botão (.fall-cta) |
Um só, verbo no imperativo curto. Dois botões nesta dobra transformam o palco em formulário de escolha. |
| Fotos | 4 por coluna, escritas duas vezes (ver Gotchas). 4 colunas × 4 = 16 posições para 8 arquivos, cada um usado duas vezes, nunca na mesma altura de colunas vizinhas. |
O que mantém a parede coesa não é o assunto das fotos, é o tratamento: mesma temperatura de cor, mesma profundidade, mesma quantidade de luz. Oito fotos do mesmo ensaio funcionam; oito fotos de bancos diferentes viram colcha de retalhos assim que duas passam lado a lado. Como cada foto aparece duas vezes na parede, o olho tem chance de comparar — e é aí que a costura aparece.
Ordem é regra editorial, não gosto. Com prefers-reduced-motion: reduce a parede congela onde nasceu e cada coluna mostra o começo do seu primeiro conjunto. As quatro primeiras fotos de cada coluna são o que essa pessoa vai ver, e mais nada. Abaixo de 900px some a quarta coluna, e abaixo de 560px somem a terceira e a quarta: as duas primeiras colunas têm de carregar as oito melhores fotos, porque são as únicas que o celular mostra.
Especificação dos assets
- Formato e dimensão mínima: WebP em retrato 3:4, mínimo 600 × 800. A coluna mede 341px de layout a 1440 de largura, e a ampliação de 1.16 a leva a ~400px na tela: 600px de fonte cobrem 1,5× e 800px cobrem 2× com folga. Acima de 900 × 1200 não há ganho visível — a foto está a 55% de opacidade, sob véu e em movimento.
- Peso alvo: até 90 KB por arquivo. São 8 arquivos e 32
<img>, mas o segundo conjunto repete as URLs do primeiro: o custo real é de 8 downloads, ~720 KB no teto. Passando disso, a parede compete com o resto da página no carregamento e as primeiras voltas engasgam. - Composição / enquadramento: assunto no terço central da vertical. A máscara apaga os 14% de cima e de baixo de cada coluna e a inclinação corta os cantos: rosto encostado na borda superior da foto simplesmente não aparece. Cenas de meio-plano (duas pessoas, mesa, sala com vista) funcionam melhor que retrato fechado, que a 55% de opacidade vira mancha.
- O que não pode ter: texto dentro da foto (passa cortado e torto); logo de instituição real; moldura, sombra ou borda no arquivo — o canto vem de
--radius-media; foto muito clara e muito estourada, que é o único caso em que o véu não segura o contraste (ver Gotchas); rosto identificável de pessoa real sem autorização — a foto não é creditada em lugar nenhum desta dobra.
Como usar
<link rel="stylesheet" href="componentes/midia/colunas-cascata/estilo.css">
Sem script. O core (tokens.css, base.css, motion.css, motion.js) é o que faz o texto entrar; a parede é animação CSS e não depende de JavaScript nenhum.
Estrutura obrigatória
<section class="sec-dark fall" data-fall>
<div class="fall-board" aria-hidden="true"> <!-- máscara mora aqui, NÃO gira -->
<div class="fall-grid"> <!-- gira e amplia -->
<div class="fall-col fall-col--up" style="--fall-dur:46s;--fall-delay:-13s">
<div class="fall-track">
<div class="fall-set"> … 4 fotos … </div>
<div class="fall-set"> … as MESMAS 4, na mesma ordem … </div>
</div>
</div>
… mais três colunas, alternando --up e --down …
</div>
</div>
<div class="fall-veil" aria-hidden="true"></div>
<div class="fall-body wrap">
<div class="sec-head fall-head"> olho · h2 · apoio · botão </div>
</div>
</section>
Ganchos
| Gancho | Papel |
|---|---|
[data-fall] |
Gancho de raiz da dobra. Nada o lê — não há script. Existe para a página hospedeira (ou o QA) encontrar a parede sem depender de uma classe de estilo. |
.fall |
A seção. Carrega todas as variáveis de ajuste; é o único lugar onde se mexe. |
.fall-board |
A caixa que não gira: dona da máscara vertical e do recorte no tamanho da seção. |
.fall-grid |
O que gira e amplia. grid-template-columns:repeat(--fall-cols,1fr) e grid-template-rows:100%. |
.fall-col |
A janela de uma coluna. Leva --fall-dur e --fall-delay no style. |
.fall-col--up / .fall-col--down |
Sentido. --down é animation-direction:reverse — não é um segundo @keyframes. |
.fall-track |
O que anda. Dois .fall-set idênticos, e gap zero (ver Gotchas). |
.fall-set |
Um conjunto de 4 fotos, com padding-block-end igual a --fall-gap. |
.fall-img |
A foto. aspect-ratio:3/4, object-fit:cover, opacidade --fall-dim. |
.fall-veil |
Véu de duas camadas: elipse escura no miolo, selo fraco nas pontas. |
.fall-body / .fall-head / .fall-title / .fall-lead / .fall-cta |
O bloco de texto. .fall-head é a primitiva .sec-head do core com --head-max:720px. |
--fall-h |
Altura do palco. clamp(560px,88vh,900px). |
--fall-cols |
Colunas: 4 no padrão, 3 abaixo de 900px, 2 abaixo de 560px. |
--fall-gap |
Vão entre colunas e entre fotos. clamp(12px,1.4vw,22px) — 20,16px a 1440. |
--fall-tilt / --fall-zoom |
Inclinação e ampliação. -6deg/1.16; -4deg/1.24 abaixo de 560px. Andam juntas (ver Gotchas). |
--fall-dim |
Opacidade da foto. .55. Primeiro parafuso a mexer quando o texto não lê. |
--fall-title |
Corpo da frase. Alimenta --display-xl da primitiva do core. |
--fall-dur |
Uma volta inteira de uma coluna, no style do .fall-col: 46s, 58s, 52s, 64s. |
--fall-delay |
Atraso negativo por coluna, no style: −13s, −31s, −7s, −22s. |
Classes de estado: nenhuma. Não há JavaScript, não há :hover e não há :focus-within. A parede é fundo, e fundo não se pausa.
Variações
- sem texto, como divisória — apague o
.fall-bodye ponha--fall-h:50vh. Vira pontuação entre duas dobras, no lugar de uma.sec-paperde respiro. Sem texto para proteger,--fall-dimsobe para.72e o.fall-veilpode sair inteiro. - sem inclinação —
--fall-tilt:0dege--fall-zoom:1.04. As colunas ficam de pé e a parede lê como grade de galeria em movimento; o zoom só existe para esconder o meio-pixel das bordas. - como hero —
--fall-h:100vh, oh2virah1, e o.fall-headganhapadding-top:var(--nav-h)se o menu fixo for opaco. Confira o zoom: tela 9:19,5 a −6° pede 1,221 (por isso o padrão do celular já cai para −4°). - cinco colunas em tela larga —
--fall-cols:5acima de 1600px. A 1440 a coluna tem 341px de layout e ~400px na tela; passando de 1600 a foto fica grande demais para ler como parede. - parede parada —
animation:noneno.fall-track, o mesmo que o bloco de movimento reduzido faz. - em seção clara — não sai trocando
.sec-darkpor.sec-paper: o véu lê--dark-rgbe continuaria escuro sob texto escuro. Enquanto não houver--light-rgbno core, troque as paradas do.fall-veilporcolor-mix(in srgb,var(--light) N%,transparent)— 78/62/22/0 no radial, 44/0/0/44 no vertical.
Gotchas
-
As fotos estão escritas duas vezes no HTML, e isso não é descuido. Cada
.fall-trackandatranslateY(-50%), que é a altura de um conjunto — é isso que faz o quadro final coincidir com o inicial. Medido a 1440: conjunto 1900px, trilho 3800px, distância entre a primeira foto de um conjunto e a do outro 1900px, erro 0 nas quatro colunas. Deixar um conjunto só faz-50%valer metade de quatro fotos, e a parede dá um solavanco por volta. Mesma receita doprova/marquee-logos, virada 90 graus. -
O
.fall-tracknão pode ganhargap, e o.fall-setnão pode perder opadding-block-end. Comgap:gno trilho a altura vira2S+ge-50%passa a serS+g/2— meio vão fora do lugar a cada volta, a falha mais difícil de diagnosticar deste arquivo. Opadding-block-enddo conjunto é o oposto: sem ele a última foto de um conjunto encosta na primeira do seguinte, e a única costura da parede fica sendo o ponto em que duas fotos se tocam. Tem de valer exatamente--fall-gap. -
Um conjunto tem de ser mais alto que a coluna. É a segunda condição do laço, e ninguém lembra dela: durante a volta o trilho sobe até
S, e o que ainda cobre a janela é o segundo conjunto. SeS < altura da coluna, abre um rasgo no fim de cada volta. Medido: conjunto 1900 contra janela de 792 (2,4×) no desktop, 1056 contra 743 no celular. Três fotos por coluna ainda passam; duas, não. -
A máscara mora no pai e o transform no filho — juntos, o desvanecimento entra torto. Máscara é aplicada no espaço local do elemento: no mesmo elemento que o
rotate, ela gira junto. Medido, trocando a máscara para o.fall-grid: a assimetria de luminância entre o canto superior esquerdo e o direito pula de 4,0 (variação normal entre duas fotos) para 23,6 — a ponta direita fica quase três vezes mais clara, com um corte diagonal visível. Geometricamente a linha de desvanecimento termina 150px mais baixo de um lado (1425 × tg 6°). Por isso são dois elementos:.fall-boardcarrega a máscara e não gira;.fall-gridgira e amplia. -
A máscara também não pode ir na
<section>. Seria um elemento a menos, mas a seção pinta o fundo (--bg, vindo do core): mascarada, ela ficaria transparente nas pontas e deixaria passar o corpo da página. Invisível num projeto todo escuro, um vinco claro no primeiro projeto que não for. -
mask-repeat:no-repeaté obrigatório. O padrão érepeat. A parede ampliada ultrapassa a caixa do.fall-boardpor construção, e o que ultrapassa receberia o gradiente ladrilhado — faixas de desvanecimento no meio da foto, a intervalos regulares. Comno-repeat, o que sai da caixa simplesmente não pinta. -
Inclinação e ampliação andam juntas, e a conta é do tamanho da tela. Para uma parede girada de θ cobrir uma caixa de W × H, o zoom precisa ser ≥
cos θ + (H/W)·sen θnas laterais e ≥cos θ + (W/H)·sen θem cima e embaixo. A 1425 × 792 com 6°: 1,053 nas laterais e 1,183 nos cantos. O padrão é 1,16 — cobre as laterais com 10% de folga e fica aquém dos cantos de propósito: a quina da tela cai 8,9px fora da parede, e esses 8,9px moram dentro dos 111px de cima que a máscara já desvanece até zero (a faixa 100% opaca pede só 1,036). Perseguir o canto geométrico custaria 2% de ampliação em todas as fotos para esconder um triângulo que já é transparente. Em tela alta e estreita a conta inverte: a 390 × 743, 6° pediriam 1,193 e por isso o celular baixa para −4°, que pede 1,130, com zoom 1,24. Mexer no--fall-tiltsem refazer essa conta descobre os cantos. -
Quem segura a barra de rolagem horizontal é o
overflow:hiddenda seção — e ele não é opcional. Medido a 1440: a seção temscrollWidth1906 contraclientWidth1425, ou seja, 481px de conteúdo girado para fora. Com ooverflow:hidden, oscrollWidthda página ficaokem 1440, 1280, 1024, 900 e 390. Sem ele, a página ganha 481px de barra. Ooverflow-x:hiddendobodyno core mascararia o sintoma no desktop e não no iOS — não conte com ele. -
O atraso é negativo, e é o que desmancha a fileira. Sem
--fall-delay, as quatro colunas começam emtranslateY(0)no mesmo quadro: a parede abre com os quatro topos de foto alinhados na mesma linha, que é exatamente o que a inclinação existe para evitar. Os valores (−13s, −31s, −7s, −22s) não são múltiplos das durações (46s, 58s, 52s, 64s) — e as durações também não têm divisor comum, então o desenho da parede leva horas para se repetir. -
O véu protege o miolo, não a dobra inteira. A conta, com foto estourada (branco quase puro) a
--fall-dim:.55sobre--dark: no centro, com o véu a 82%, o fundo efetivo fica em torno de#222e o texto--fgfecha ~14:1; na borda da seção, onde o véu já é zero, o mesmo pixel fecha 2,8:1. Por isso o texto vive no.sec-headde 720px, centrado, e por isso--fall-dim— e não a opacidade do véu — é o primeiro parafuso a mexer quando alguém traz fotos mais claras: baixar--fall-dimpara.42preserva a parede; engrossar o véu apaga a parede inteira e sobra uma dobra escura com texto no meio. -
loading="lazy"no segundo conjunto é de graça, e no primeiro seria um buraco. O segundo conjunto aponta para as mesmas 8 URLs do primeiro — por construção, já que os dois têm de ser idênticos —, então nunca dispara um download que o primeiro não tenha disparado: quando entra na tela, é acerto de cache. Medido, forçando o laço para 50%, com o segundo conjunto na tela: 0 buracos em 12 fotos visíveis. No primeiro conjunto seria diferente: com o atraso negativo, a foto visível no quadro zero pode ser qualquer uma das quatro, elazynelas põe a parede para carregar depois de aparecer. -
A parede não pausa no hover, e isso é decisão, não esquecimento.
prova/marquee-logoseprova/carrossel-depoimentospausam porque o item da esteira é conteúdo — há um nome para ler. Aqui não há nada para ler na parede, e ela ocupa a tela inteira: pausar no hover significaria a dobra congelar sempre que o ponteiro atravessasse a página. Pelo mesmo motivo não há:focus-within— chegar no botão pelo teclado não pode parar o fundo. -
grid-template-rows:100%parece redundante e não é. Linha implícita éauto, eautoem grid dimensiona pelo conteúdo: sem essa linha, a linha nasce com a altura do trilho (3.797px medidos a 1440), a coluna herda essa altura e a caixa da parede deixa de ter qualquer relação com a caixa da seção. Continua funcionando por acidente — quem recorta é a máscara —, mas toda medida feita depois passa a mentir,getBoundingClientRectincluído. -
Nada aqui deve ser medido com
getBoundingClientRect. A parede está girada, e o retângulo devolvido é a caixa alinhada aos eixos: a "altura" de um conjunto sai 2233 em vez de 1900, e a emenda parece ter 20px de erro que não existe. Para conferir o laço, useoffsetTopeoffsetHeight, que são espaço de layout e ignoram o transform.
Histórico
| Projeto | Onde | Data | Diferenças |
|---|---|---|---|
| autoral | construído do zero na biblioteca | 2026-09-03 | — |
Origem. Padrão de interação recorrente em site de produto premium: a família de templates de Framer que abre com uma wall of photos, as páginas de coleção da Apple TV+ e do Nike SNKRS, e boa parte dos heros de galeria premiados no Awwwards. Não há referência única: o que se repete nessa família é a ideia — imagens pequenas passando em colunas opostas, tudo levemente torto, e a promessa parada no centro — e as proporções que a fazem funcionar (a inclinação entre 4° e 8°, a esteira lenta o bastante para não virar ruído, o texto ocupando menos de metade da largura). Nada foi copiado: reescrito do zero dentro do contrato desta biblioteca.
Decisões de tradução para o contrato.
- O movimento saiu do JavaScript. A implementação comum desse padrão é um ticker que reescreve
transforma cada quadro, ou umScrollTriggercomscrub. Aqui são duas regras de CSS (@keyframes fallRune uma linha deanimation), o arquivoscript.jsnão existe, e o@media(prefers-reduced-motion)desliga tudo na própria folha — sem depender de o script ler flag nenhuma. - Tudo o que era número virou variável do prefixo: inclinação, ampliação, número de colunas, vão, altura do palco, opacidade da foto e corpo do título na raiz; velocidade e atraso por coluna, no
styledo.fall-col, porque são valores por instância e não por projeto. - A máscara foi separada do transform em dois elementos, com a razão medida e registrada nos Gotchas. É a primeira máscara de duas pontas no eixo vertical da biblioteca: as treze anteriores são o véu ancorado embaixo do §6 do contrato, e a de duas pontas só existia no eixo horizontal, no
prova/carrossel-depoimentos— cuja ficha já avisava que a terceira ocorrência promove a receita a primitiva do core. - O título não escreve
font-size: alimenta--display-xle--display-xl-lhda primitiva.display-xldo core, com entrelinha1.08em vez do.95padrão, que serve a título de uma ou duas palavras e não a duas linhas de frase inteira. O cabeçalho é a primitiva.sec-head, com--head-max:720px; o apoio é.sec-leadcommargin-inline:autopor seletor composto, para a regra vencer independentemente da ordem de carregamento (§6.4). - Acessibilidade que o padrão comum não tem: a parede inteira é
aria-hidden="true"e as 32 imagens vão comalt=""— inclusive as do primeiro conjunto, o que é a diferença para as esteiras horizontais da casa, onde o primeiro conjunto é conteúdo. Aqui nenhuma foto é argumento, e descrever cada uma seria despejar 32 legendas irrelevantes. Não há nada focável dentro da parede (medido: 1 elemento focável na seção, 0 dentro dearia-hidden), e o anel de foco do botão sobra 227px da borda mais próxima, longe dooverflow:hiddenque o cortaria. - A entrada do texto é do core:
data-rv/data-dno olho, no apoio e no botão,data-splitna frase. Nenhum observer próprio, nenhum keyframe de entrada.
Divergências conhecidas vs. a família de referência.
- As fotos ficam grandes no desktop. A 1440 a coluna tem 341px de layout e ~400px na tela, e cabem uma foto e meia na altura do palco: lê mais como "painéis inclinados" do que como "muitas imagens pequenas passando". A família de referência costuma rodar 5 ou 6 colunas em telas largas. Quatro é o padrão porque abaixo de 900px já é preciso esconder colunas, e cinco no desktop com duas no celular é uma queda grande demais; quem tiver material para isso tem a variação cinco colunas.
- A fonte do preview não é a do projeto. As licenciadas ficam fora do repositório, e o preview cai na pilha do sistema: o corpo medido do título (62px a 1440, 41px a 900, 25,7px a 390) desenha a mancha certa nessa pilha. Numa display condensada, a mesma frase encolhe e o
--fall-titleprecisa subir — o que se persegue é a fração da coluna que a linha ocupa, não o número. - Não roda em
.sec-papersem edição. O véu lê--dark-rgb, e o core não tem--light-rgb. A variação em seção clara descreve a troca porcolor-mix; a solução limpa é um token novo, e token novo é decisão de arquitetura (§3) — fica registrado como proposta, não aplicado aqui.