Um painel de 100vh que está atrás da página desde o primeiro pixel de rolagem, parado, coberto pelas dobras. Quando a última dobra sobe, ele é descoberto — e como nunca se move, a sensação é de parallax sem que exista parallax nenhum. Dentro dele, cinco faixas repartindo a tela por igual: links e chamada em cima, filete, a marca em corpo gigante no meio, filete, e a linha legal embaixo.
Resolve o fim de página quando o rodapé não é sobra de layout e sim uma dobra com peso próprio. O rodapé comum entra rolando junto com o resto e some da memória; este chega como uma virada — a página se abre e revela algo que estava ali o tempo todo.
Sem JavaScript. O efeito são três regras de CSS.
Quando usar
- Última dobra de site institucional, portfólio ou landing de marca — onde o fim da página é um momento e não um rodapé de suporte.
- Quando o rodapé tem pouca coisa: uma fila de links, uma chamada, a marca e a linha legal. Cinco faixas é o teto.
- Quando existe uma marca (nome ou logotipo) que aguenta corpo gigante.
Quando não usar
- Rodapé com muito link: quatro colunas de navegação, endereço, redes, selos. Isso é
rodape/quatro-colunas, e ele não precisa de invólucro nenhum na página. - Quando a página não pode ganhar o invólucro
.curt-pagina— em CMS que não deixa envolver o conteúdo todo, este componente não entra. Ver o gotcha. - Páginas de aplicação, painel ou qualquer coisa com rolagem interna: o vazio de 100vh no fim é um custo que só se paga em página de leitura.
Padrão editorial
| Slot | Regra |
|---|---|
| Links | 4 a 6, uma linha só. É a navegação de repescagem, não o mapa do site. |
| Chamada | Um botão, um verbo. É o último pedido da página. |
| Marca | O nome, escrito normal — a caixa alta é do CSS. Alvo: encher a largura da coluna em uma linha. |
| Linha legal | Copyright de um lado, crédito do outro. Nada mais. |
O que mantém o painel de pé é o vazio. Cinco faixas em 100vh deixam muito ar entre elas de propósito: é esse ar que faz a dobra parecer o fim de alguma coisa. Acrescentar uma sexta faixa — endereço, newsletter, selo — fecha o espaço e o rodapé volta a ser um rodapé.
Especificação dos assets
- Nenhum. A marca é texto.
- Na variação com logotipo: SVG de traço único, largura livre, sem margem interna. O arquivo ocupa a largura da coluna e a altura vem da proporção dele.
Como usar
<link rel="stylesheet" href="componentes/rodape/cortina-fixa/estilo.css">
Este componente pede uma coisa da página. Todo o conteúdo acima do rodapé tem de estar dentro de um invólucro:
<body>
<div class="curt-pagina">
<!-- hero, todas as dobras, tudo -->
</div>
<div class="curt-espaco" aria-hidden="true"></div>
<footer class="sec-dark curt"> ... </footer>
</body>
Ganchos
| Gancho | Papel |
|---|---|
.curt-pagina |
Invólucro de tudo que vem antes. É ele que levanta a página acima do painel (z-index:1). |
.curt-espaco |
O vazio de 100vh no fim do fluxo, fora do invólucro. É ele que dá o trecho de rolagem em que o painel aparece. |
--curt-linha |
Cor dos dois filetes. var(--rule-2) no padrão. |
--curt-marca |
Corpo da marca do meio. clamp(34px,9.6vw,140px) no padrão. |
Variações
- logotipo no lugar do nome —
<img class="curt-marca-img">no lugar do<p class="curt-marca">. - filete forte —
--curt-linha:color-mix(in srgb,var(--fg-2) 70%,transparent), que é o peso do original. - em painel claro —
.sec-paperno lugar de.sec-dark. - sem cortina — apague o
.curt-espacoe ponhaposition:staticno.curt; vira um rodapé de tela cheia comum e o invólucro deixa de ser necessário.
Gotchas
- O invólucro
.curt-paginanão é opcional, e não dá para evitar. CSS não seleciona irmãos anteriores, então o componente não consegue erguer as dobras por conta própria — alguém tem de dizer quem fica por cima. Esquecer o invólucro faz o painel cobrir o site inteiro: falha barulhenta de propósito, porque erro silencioso aqui (uma dobra transparente deixando o preto vazar no meio da página) seria muito pior de achar. z-index:0no painel, nunca negativo. A tentação é usarz-index:-1para ficar atrás sem pedir nada à página. Não funciona: elemento com z-index negativo pinta abaixo da caixa do<body>, que cobre a tela toda. O painel some — e, pior, os links do rodapé ficam inclicáveis, porque o body ganha o teste de acerto no lugar deles. Medido comelementFromPoint, não deduzido: no ponto do primeiro link, o alvo devolvido eraBODY.pointer-events:noneno.curt-espacoé obrigatório. Ele é um bloco transparente de 100vh por cima do painel; sem isso engoliria todo clique nos links do rodapé — o mesmo problema do item acima, por outra porta.- O espaçador fica FORA do
.curt-pagina. Dentro dele herdaria oz-index:1e voltaria a cobrir o painel. - Toda dobra da página precisa de fundo opaco. O core já dá isso a
<section>(background:var(--bg)), mas só dentro de um escopo de tom: uma<section>sem.sec-dark/.sec-paperfica com--bgindefinido, e o painel do rodapé vaza no meio da página. - Não use
data-rvdentro do rodapé. O painel está na tela desde o carregamento, só que coberto: oIntersectionObserverdo core dispara no primeiro segundo e a entrada é gasta antes de qualquer pessoa ver. A revelação é a cortina. - Não há bloco de
prefers-reduced-motion, e não é esquecimento. O painel não anima e não se desloca em momento nenhum — quem se move é a página, rolando. O que muda no hover é cor, que não é movimento. Não existe o que desligar. 100vhno espaçador, de propósito. Em celular,100vhé a viewport grande (com a barra do navegador recolhida), que é sempre maior ou igual à altura real — assim o painel termina de aparecer mesmo com a barra à mostra. Trocar por100svhfaria a cortina parar antes do fim em metade dos aparelhos.
Histórico
| Projeto | Onde | Data | Diferenças |
|---|---|---|---|
| referência: Rony Meisler | https://www.ronymeisler.com.br/investimentos — rodapé | 2026-08-31 | referência visual, não origem de código |
Normalizações aplicadas na tradução: a referência é Framer — classes hash, sem licença declarada, e nada foi copiado (§11). Medido nela e reproduzido: painel position:fixed de 1425×900 com z-index:1 sobre um div vazio de 900px (100vh) no fim do fluxo, com as dobras da página em z-index:3; miolo de 1265×740 com padding 80 e justify-content:space-between, o que dá quatro vãos iguais de 106; linha de topo de 48 com cinco links 16/24 em gap 32 e botão pílula de 182×48 com raio 100px; dois filetes de 1px; faixa central de 1265×242 para o logotipo; linha de base de 24. E o que mais importava confirmar: transform:none em cinco posições de rolagem diferentes — o painel nunca se move, e é daí que vem a sensação de parallax.
O que é nosso: o padding lateral saiu para o .wrap, que continua sendo a única regra da coluna (§4); preto cravado e o cinza-sage dos filetes viraram --bg, --fg, --fg-2 e --rule-2 pelo escopo de tom; o botão é a primitiva .btn/.btn-ghost do core, sem retoque de forma; a nav do rodapé virou <nav> com rótulo; o espaçador ganhou aria-hidden e pointer-events:none.
Divergências conhecidas vs. a referência: o painel lê --dark (#0a0a0a) e não preto puro — é a superfície escura do projeto, e o §3 do contrato pede tons vizinhos. Os filetes saem de --rule-2 e ficam bem mais discretos que o rgb(186,191,186) quase branco do original; quem quiser aquele peso sobe --curt-linha, que existe para isso. A faixa central do original é um canvas Rive com o logotipo animado — uma lib inteira para um logotipo que se mexe; aqui é texto em --display, que escala sozinho, não pede asset e não some se o SVG do cliente não chegou. O corpo da marca é calibrado para um nome de ~14 caracteres encher a coluna numa linha: nome muito diferente disso pede ajuste em --curt-marca, e é o único ajuste que a troca de marca pede.