← Biblioteca componentes/midia/colunas-cascata/ficha.md CódigoTela cheia
midia/colunas-cascata

Colunas em cascata

🟢 estavel complexidade média prefixo fall- parede-de-fotos esteira-vertical loop-infinito movimento-continuo sem-js mascara-vertical inclinacao sangria cta fundo-animado retrato sem javascript

Incluir · as linhas que se colam num site novo, depois do core (CLAUDE.md §10)

<link rel="stylesheet" href="componentes/midia/colunas-cascata/estilo.css">

markup.html · 142 linhas · 8,3 KB

<!-- ==========================================================================
     COLUNAS EM CASCATA — parede de fotos em movimento, promessa parada
     Prefixo: fall-
     Requer: core (tokens, base, motion.css, motion.js) + estilo.css
     SEM JavaScript. As quatro esteiras são animação de CSS até -50%.

     DUAS CAMADAS QUE NÃO SE MISTURAM. Atrás, quatro colunas de fotos que
     sobem e descem sem parar, inclinadas e ampliadas até sangrar as quatro
     bordas. À frente, parado no centro, o que a dobra tem a dizer: olho,
     frase e botão. O padrão inteiro existe por causa dessa divisão de
     trabalho — o movimento segura o olho, o texto não se mexe para poder ser
     lido. Trocar isso de lado (texto em movimento sobre foto parada) é outro
     componente e outro problema.

     A PAREDE É DECORAÇÃO, E O MARKUP DIZ ISSO. O .fall-board inteiro leva
     aria-hidden="true" e TODAS as fotos vão com alt="" — inclusive as do
     primeiro conjunto. É a diferença para o prova/carrossel-depoimentos e o
     prova/marquee-logos, onde o primeiro conjunto é conteúdo (aquelas
     pessoas e aquelas marcas são o argumento da dobra) e só a cópia se cala.
     Aqui nenhuma das 32 imagens é argumento: são textura atrás de uma frase,
     recortadas, tortas, a 55% de opacidade e passando. Descrever cada uma
     seria despejar 32 legendas irrelevantes em quem usa leitor de tela.

     ESTRUTURA OBRIGATÓRIA DO LAÇO. Cada .fall-track contém DOIS .fall-set
     idênticos, de quatro fotos cada. A animação anda -50% da altura do
     trilho, que é exatamente a altura de um conjunto — é isso que faz o
     quadro final coincidir com o inicial. Acrescentar ou tirar uma foto
     significa fazê-lo nos DOIS conjuntos da mesma coluna, na mesma ordem.
     Mesma receita das esteiras horizontais da casa, virada 90 graus.

     A MÁSCARA E O TRANSFORM MORAM EM ELEMENTOS DIFERENTES, e por isso são
     dois níveis (.fall-board e .fall-grid) em vez de um. A máscara de um
     elemento é aplicada no espaço LOCAL dele: posta junto com o rotate, ela
     giraria junto e o desvanecimento das pontas entraria torto na tela —
     151px de diferença de altura entre a ponta esquerda e a direita a 1440
     de largura. O pai carrega a máscara e não gira; o filho gira e amplia.

     SEM .sec-pad, e não é esquecimento. Esta dobra é um PALCO de altura
     própria (--fall-h), com o texto centrado nele por grid. O respiro de
     cima e de baixo é a altura do palco menos o bloco de texto; o
     padding de --sec-gap só comeria o palco por dentro, sem mudar nada do
     que se vê. O ritmo entre esta dobra e as vizinhas continua vindo do
     .sec-pad DELAS.

     ORDEM EDITORIAL. Com "reduzir movimento" ligado 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, portanto, as que essa pessoa
     vai ver — ponha ali as melhores.
     ========================================================================== -->

<section class="sec-dark fall" data-fall>

  <!-- A parede. Decoração: nada aqui é anunciado e nada aqui é focável. -->
  <div class="fall-board" aria-hidden="true">
    <div class="fall-grid">

      <div class="fall-col fall-col--up" style="--fall-dur:46s;--fall-delay:-13s">
        <div class="fall-track">
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-1.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-5.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-3.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-7.webp" alt="" decoding="async">
          </div>
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-1.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-5.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-3.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-7.webp" alt="" loading="lazy" decoding="async">
          </div>
        </div>
      </div>

      <div class="fall-col fall-col--down" style="--fall-dur:58s;--fall-delay:-31s">
        <div class="fall-track">
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-2.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-6.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-4.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-8.webp" alt="" decoding="async">
          </div>
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-2.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-6.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-4.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-8.webp" alt="" loading="lazy" decoding="async">
          </div>
        </div>
      </div>

      <div class="fall-col fall-col--up" style="--fall-dur:52s;--fall-delay:-7s">
        <div class="fall-track">
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-8.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-3.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-6.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-1.webp" alt="" decoding="async">
          </div>
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-8.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-3.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-6.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-1.webp" alt="" loading="lazy" decoding="async">
          </div>
        </div>
      </div>

      <div class="fall-col fall-col--down" style="--fall-dur:64s;--fall-delay:-22s">
        <div class="fall-track">
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-4.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-7.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-2.webp" alt="" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-5.webp" alt="" decoding="async">
          </div>
          <div class="fall-set">
            <img class="fall-img" src="../../../showroom/demo/ph-4.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-7.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-2.webp" alt="" loading="lazy" decoding="async">
            <img class="fall-img" src="../../../showroom/demo/ph-5.webp" alt="" loading="lazy" decoding="async">
          </div>
        </div>
      </div>

    </div>
  </div>

  <!-- Véu: escurece o miolo, onde o texto mora, e solta as bordas para a
       parede continuar aparecendo. Sem ele, uma foto clara passando atrás da
       frase derruba o contraste de 12,9:1 para 2,8:1. -->
  <div class="fall-veil" aria-hidden="true"></div>

  <div class="fall-body wrap">
    <div class="sec-head fall-head">
      <span class="eyebrow" data-rv>Para famílias e empresários</span>
      <h2 class="fall-title display-xl" data-split>Patrimônio é o que fica<br>quando a pressa passa.</h2>
      <p class="sec-lead fall-lead" data-rv data-d="1">Assessoria independente para quem quer decidir com calma, entender cada ativo e deixar o próximo passo organizado.</p>
      <a class="btn btn-primary fall-cta" href="#diagnostico" data-rv data-d="2">Agende seu diagnóstico</a>
    </div>
  </div>

</section>

estilo.css · 242 linhas · 10,7 KB

/* ==========================================================================
   COLUNAS EM CASCATA — prefixo fall-
   Quatro esteiras verticais de foto, inclinadas e ampliadas até sangrar as
   bordas, com as pontas desvanecendo em máscara. Por cima, parado no centro,
   o bloco de texto da dobra.

   Tokens LIDOS:
     --dark-rgb          o véu do miolo e o selo das pontas (opacidade sobre
                         a superfície escura do projeto, nunca cor crua)
     --rule              tinta da caixa da foto enquanto ela não chega
     --radius-media      canto da foto

   Tokens ESCRITOS (parafusos de primitiva do core, ver core/base.css):
     --display-xl        corpo do título, alimentado aqui por --fall-title
     --display-xl-lh     entrelinha dele
     --head-max          largura do .sec-head

   A cor do texto não aparece neste arquivo: vem do escopo de tom da seção
   pelas classes do core (.eyebrow, .display-xl, .sec-lead, .btn). A coluna
   também não: é o .wrap, e ele não é refeito aqui (contrato §4).

   SEM JavaScript. O laço é CSS puro: cada trilho anda -50% da própria
   altura, que é a altura exata de um dos dois conjuntos idênticos.

   TRÊS DECISÕES QUE EXPLICAM O ARQUIVO INTEIRO:

   1. A máscara mora no .fall-board e o transform no .fall-grid, que é filho
      dele. Máscara é aplicada no espaço LOCAL do elemento: no mesmo elemento
      que o rotate, ela gira junto e o desvanecimento entra torto na tela.

   2. Só transform anima. A opacidade da foto, o véu e a máscara são estados
      fixos; o que se move é um translateY por coluna, em quatro camadas
      compostas. Nenhum filter, nenhum blur e nenhuma sombra em laço.

   3. Nada aqui sobrescreve o escopo de tom. O texto lê --fg herdado da
      seção; o véu é opacidade sobre --dark-rgb. Trocar .sec-dark por
      .sec-deep funciona sem tocar no componente — .sec-paper não (ver ficha).
   ========================================================================== */

.fall{
  /* Altura do palco. Não é uma dobra de altura de conteúdo: é um quadro, e
     o texto é centrado dentro dele. 88vh e não 100vh de propósito — a fatia
     da dobra seguinte aparecendo embaixo é o que diz "a página continua". */
  --fall-h:clamp(560px,88vh,900px);

  --fall-cols:4;                    /* colunas da parede (3 ≤900px, 2 ≤560px) */
  --fall-gap:clamp(12px,1.4vw,22px);/* vão entre colunas E entre fotos */

  /* Inclinação e ampliação andam juntas: girar sem ampliar descobre os
     cantos. A conta que liga as duas está na ficha; em resumo, para a
     parede cobrir a largura da tela é preciso
        zoom >= cos(tilt) + (altura/largura) * sen(tilt).
     1.16 cobre com folga em qualquer proporção de desktop; abaixo de 560px
     a tela fica alta e estreita, a conta sobe, e por isso lá o tilt cai
     para -4deg e o zoom sobe para 1.24. */
  --fall-tilt:-6deg;
  --fall-zoom:1.16;

  /* Opacidade da foto em repouso. É o primeiro parafuso a mexer se o texto
     ficar difícil de ler sobre uma parede clara — antes de engrossar o véu,
     que apaga a parede inteira. */
  --fall-dim:.55;

  /* Corpo da frase. Alimenta os dois parafusos da primitiva .display-xl do
     core (ver core/base.css). A entrelinha é 1.08, não o .95 padrão: aquele
     serve a título de uma ou duas palavras, e aqui são duas linhas de frase
     inteira, com ascendente e descendente para não colidirem. */
  --fall-title:clamp(28px,4.6vw,62px);
  --display-xl:var(--fall-title);
  --display-xl-lh:1.08;

  position:relative;
  /* Quem segura a ampliação é este overflow. Sem ele, o .fall-grid ampliado
     vazaria para fora da seção e a página ganharia barra horizontal. */
  overflow:hidden;

  /* min-height e não height: o palco tem a altura do quadro, mas se a frase
     crescer (tradução mais longa, corpo maior no tema) a seção cresce junto
     em vez de cortar o texto no overflow acima. */
  min-height:var(--fall-h);
  display:grid;
  place-items:center;
}

/* ---------------------------------------------------------------- parede */
/* O board não gira: ele existe para carregar a máscara das pontas e para
   recortar a parede no tamanho da seção. A máscara fica aqui, e não na
   própria .fall, porque a seção também 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, e aí o que
   ultrapassa a caixa (a parede ampliada ultrapassa) receberia o gradiente
   ladrilhado, com faixas de desvanecimento no meio da foto. */
.fall-board{
  position:absolute;inset:0;z-index:0;
  mask-image:linear-gradient(to bottom,transparent 0,#000 14%,#000 86%,transparent 100%);
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 14%,#000 86%,transparent 100%);
  mask-repeat:no-repeat;
  -webkit-mask-repeat:no-repeat;
}

/* O que gira e amplia. Uma linha de grid, --fall-cols colunas iguais.

   grid-template-rows:100% é obrigatório e não é decoração. Linha implícita é
   auto, e auto em grid dimensiona pelo CONTEÚDO: a linha nasceria com a
   altura do trilho (3.797px medidos a 1440), a coluna herdaria essa altura e
   a caixa da parede deixaria de ter relação com a caixa da seção. Continua
   funcionando por acidente — o corte vem da máscara — mas qualquer medida
   feita depois (a minha inclusive) passa a mentir. Com 100% a coluna é a
   janela de --fall-h, e o que passa por ela é o trilho. */
.fall-grid{
  position:absolute;inset:0;
  display:grid;
  grid-template-columns:repeat(var(--fall-cols),1fr);
  grid-template-rows:100%;
  gap:var(--fall-gap);
  transform:rotate(var(--fall-tilt,-6deg)) scale(var(--fall-zoom,1.16));
}

/* A coluna é só a caixa: quem transborda é o trilho, e o corte vem do
   board acima. Sem overflow próprio aqui — seriam mais quatro contextos de
   composição sem nada a recortar que a máscara já não recorte. */
.fall-col{min-width:0}

/* O QUE ANDA. Uma volta inteira é a altura de UM conjunto, e o trilho tem
   dois — daí o -50%. O trilho não pode ganhar gap: com gap g entre os dois
   conjuntos, a altura vira 2S+g e -50% passa a ser S+g/2, meio vão fora do
   lugar a cada volta.

   O delay é NEGATIVO e diferente por coluna (no style do .fall-col). Sem
   ele as quatro colunas começam alinhadas no quadro zero e a parede abre
   com uma fileira de topos na mesma linha, que é justamente o que a
   inclinação existe para desmanchar. */
.fall-track{
  display:flex;flex-direction:column;
  will-change:transform;
  animation:fallRun var(--fall-dur,52s) linear infinite;
  animation-delay:var(--fall-delay,0s);
}
/* Colunas vizinhas em sentidos opostos. reverse e não um segundo keyframe:
   os dois extremos do laço são o mesmo quadro, então a mesma animação
   servida de trás para frente fecha igual. */
.fall-col--down .fall-track{animation-direction:reverse}

@keyframes fallRun{
  from{transform:translateY(0)}
  to{transform:translateY(-50%)}
}

/* A parede congela onde nasceu, e o texto lê igual — ele nunca dependeu do
   movimento. Cada coluna passa a mostrar o começo do seu primeiro conjunto:
   por isso a ficha manda pôr as melhores fotos nas quatro primeiras. */
@media(prefers-reduced-motion:reduce){
  .fall-track{animation:none}
}

/* O padding embaixo do conjunto é a EMENDA, não sobra: o gap separa as
   fotos dentro de um conjunto e não separa a última foto de um conjunto da
   primeira do seguinte. Tem de valer exatamente --fall-gap; qualquer outro
   valor troca o encontro sem vão por um vão diferente dos demais, uma vez
   por volta, sempre no mesmo ponto. */
.fall-set{
  display:flex;flex-direction:column;
  gap:var(--fall-gap);
  padding-block-end:var(--fall-gap);
}

.fall-img{
  width:100%;
  aspect-ratio:3/4;
  object-fit:cover;
  border-radius:var(--radius-media);
  /* Opacidade na FOTO, não no board. No board seria uma propriedade só,
     mas um grupo com opacidade obriga o navegador a compor a subárvore
     inteira a cada quadro — e a subárvore inteira é o que está animando. */
  opacity:var(--fall-dim);
  /* Caixa da foto enquanto ela não chega: sem isso a parede pisca em
     retângulos do fundo da seção durante o carregamento. */
  background:var(--rule);
}

/* ---------------------------------------------------------------- véu */
/* Duas camadas. A elipse escurece o miolo, que é onde o texto mora, e solta
   as bordas para a parede continuar visível — véu chapado apagaria a foto
   toda e sobraria uma dobra escura com texto no meio. O gradiente vertical
   é fraco e sela as pontas contra as dobras vizinhas, como o .hero-fade.
   Sem backdrop-filter: seria desfoque sobre a área inteira da tela, com
   quatro esteiras se movendo por baixo. */
.fall-veil{
  position:absolute;inset:0;z-index:1;
  pointer-events:none;
  background:
    radial-gradient(68% 74% at 50% 50%,
      rgba(var(--dark-rgb),.82) 0%,
      rgba(var(--dark-rgb),.66) 34%,
      rgba(var(--dark-rgb),.24) 68%,
      rgba(var(--dark-rgb),0) 100%),
    linear-gradient(to bottom,
      rgba(var(--dark-rgb),.46) 0%,
      rgba(var(--dark-rgb),0) 30%,
      rgba(var(--dark-rgb),0) 70%,
      rgba(var(--dark-rgb),.46) 100%);
}

/* ---------------------------------------------------------------- texto */
/* O .wrap continua sendo a única coluna (contrato §4). O que esta classe
   faz é só pôr o bloco acima do véu — a centralização vem do grid da seção. */
.fall-body{position:relative;z-index:2}

/* Cabeçalho centrado é a primitiva .sec-head do core; aqui só a largura.
   720px em vez dos 760 padrão para a frase quebrar onde o <br> manda,
   também nas telas médias. */
.fall-head{--head-max:720px}

.fall-title{margin-top:clamp(14px,2vh,22px)}
.fall-cta{margin-top:clamp(24px,4vh,34px)}

/* ---------------------------------------------------------------- mobile */
/* A quarta coluna sai de cena em vez de espremer: quatro colunas em 900px
   dão foto de 210px, e a 3/4 isso é um selo, não uma imagem. O display:none
   é obrigatório junto com --fall-cols — o grid manda o filho que sobra para
   uma segunda linha, e a parede viraria duas fileiras. */
@media(max-width:900px){
  .fall{--fall-cols:3}
  .fall-col:nth-child(4){display:none}
}

/* Tela alta e estreita: a conta do zoom sobe (a diagonal da tela pesa mais)
   e a inclinação tem de baixar, senão os cantos descobrem. -4deg pede 1.13
   em 390x742; 1.24 cobre também os aparelhos 9:19.5 e sobra margem para a
   variação de tela cheia. */
@media(max-width:560px){
  .fall{
    --fall-cols:2;
    --fall-tilt:-4deg;
    --fall-zoom:1.24;
    --fall-title:clamp(24px,6.6vw,30px);
  }
  .fall-col:nth-child(n+3){display:none}
}

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

Quando não usar


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


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


Gotchas


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.

Divergências conhecidas vs. a família de referência.