← Biblioteca componentes/efeitos/desfoque-progressivo/ficha.md CódigoTela cheia
efeitos/desfoque-progressivo

Desfoque progressivo na borda da tela

🟢 estavel complexidade baixa prefixo dsf- desfoque blur progressive-blur borda backdrop-filter mascara sticky decoracao css-puro sem-js sem javascript

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

<link rel="stylesheet" href="componentes/efeitos/desfoque-progressivo/estilo.css">

markup.html · 71 linhas · 4,3 KB

<!-- ==========================================================================
     DESFOQUE PROGRESSIVO — a página perde o foco ao chegar na borda da tela
     Prefixo: dsf-
     Requer: core/tokens.css, core/base.css + este estilo.css. Sem script.

     O QUE SE COLA É UM <div> COM CINCO <span> VAZIOS, e o lugar dele é a
     regra inteira: o .dsf é o ÚLTIMO FILHO do trecho da página em que a
     faixa vale. Ele gruda na borda de baixo da janela enquanto o trecho
     passa e para quando o trecho acaba — por isso o rodapé, que vem DEPOIS
     do trecho, entra nítido.

     O trecho é qualquer elemento que embrulhe as dobras: o <main>, ou um
     <div> como o daqui. O que NÃO pode ser:
       - um [data-rv] ou qualquer ancestral com `filter` ou `opacity` menor
         que 1 — vira raiz de backdrop, e as camadas passam a desfocar só o
         que está DENTRO dele (MANUAL §5);
       - um elemento com overflow diferente de visible — vira contêiner de
         rolagem e o sticky gruda nele, não na janela.

     As três seções abaixo são CONTEXTO, e no site real são as dobras que
     você já tem. Estão dentro do markup, e não no preview-antes.html, pela
     mesma regra estrutural: sem elas o .dsf não tem trecho a acompanhar.
     O que se cola é o que está entre o comentário de início e o de fim da
     peça, mais adiante.
     ========================================================================== -->

<div>

  <!-- CONTEXTO — dobras quaisquer. Não fazem parte do componente. -->
  <section class="sec-dark sec-pad">
    <div class="wrap">
      <span class="eyebrow" data-rv>Como trabalhamos</span>
      <h2 class="display-xl ttl">Patrimônio pede método, não palpite.</h2>
      <p class="sec-lead" data-rv data-d="1">Antes da primeira aplicação existe uma política escrita: faixas de alocação, liquidez mínima, horizonte de cada objetivo. A carteira vem depois e presta contas a ela, trimestre a trimestre.</p>
      <p class="sec-lead" data-rv data-d="2">Role a página devagar. A borda de baixo da tela não corta o texto em linha reta: ele vai perdendo o foco nos últimos centímetros, como uma lente com pouca profundidade de campo.</p>
    </div>
  </section>

  <section class="sec-deep sec-pad">
    <div class="wrap">
      <span class="eyebrow" data-rv>Independência</span>
      <h2 class="sec-title ttl">A remuneração é <em>uma taxa</em>, nunca comissão</h2>
      <p class="sec-lead" data-rv data-d="1">Quem recomenda não ganha com o produto recomendado. A taxa é acordada antes, sobre o patrimônio sob consultoria, e aparece no mesmo relatório que mostra a carteira.</p>
      <p class="sec-lead" data-rv data-d="2">A custódia fica na instituição que a família já usa. Muda quem decide a alocação e a quem essa pessoa responde — o dinheiro não sai de onde está.</p>
      <p class="sec-lead" data-rv data-d="3">Três sócios assinam cada política de investimento, e nenhuma é alterada sem o registro de quem pediu, quando e por quê.</p>
    </div>
  </section>

  <section class="sec-dark sec-pad">
    <div class="wrap">
      <span class="eyebrow" data-rv>Rotina</span>
      <h2 class="sec-title ttl">O que acontece <em>depois</em> da assinatura</h2>
      <p class="sec-lead" data-rv data-d="1">Uma reunião por trimestre com a carteira inteira na mesa, um relatório mensal em linguagem de gente, e um telefone que atende quando o mercado faz barulho.</p>
      <p class="sec-lead" data-rv data-d="2">Nada disso é diferencial: é o mínimo que alguém deveria receber de quem cuida do patrimônio da família. O diferencial é fazer sempre, inclusive nos trimestres em que não há nada de novo para contar.</p>
    </div>
  </section>

  <!-- ===================== COMPONENTE — INÍCIO ===================== -->
  <!-- Último filho do trecho, sempre. aria-hidden porque é pura decoração:
       nenhuma das cinco camadas tem conteúdo. Na borda de cima, a classe
       .dsf--topo e o lugar de PRIMEIRO filho. -->
  <div class="dsf" aria-hidden="true">
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
  </div>
  <!-- ====================== COMPONENTE — FIM ====================== -->

</div>

estilo.css · 157 linhas · 7,7 KB

/* ==========================================================================
   DESFOQUE PROGRESSIVO — prefixo dsf-
   Uma faixa colada à borda de baixo da janela em que a página vai perdendo
   o foco à medida que chega na borda: nítida a uma altura de dedo, desfeita
   no rodapé da tela. CSS puro — nenhuma linha de JavaScript.

   Tokens usados:
     --dark     fallback da tinta opcional da borda (--dsf-cor)

   POR QUE CINCO CAMADAS, E NÃO UMA COM MÁSCARA. Um `backdrop-filter` só tem
   UM raio. Uma camada de 14px com máscara em gradiente não desfoca "aos
   poucos": desfoca tudo a 14px e esmaece o resultado, e no meio da faixa o
   que se vê são duas imagens sobrepostas — a nítida e a borrada — em vez de
   uma imagem meio desfocada. Lê como fantasma. A saída é empilhar camadas
   de raio crescente (1/16, 1/8, 1/4, 1/2 e o raio cheio), cada uma com uma
   JANELA de máscara que só a deixa valer numa fatia da faixa. As janelas se
   sobrepõem pela metade, e por isso a passagem de um raio para o seguinte
   não tem degrau. É o desenho do "progressive blur" do Framer, que a
   referência usa com oito camadas; aqui são cinco, e o porquê está na
   ficha.

   É A REGRA DO VÉU DO §6 LEVADA AO LIMITE: nenhuma das cinco camadas é
   caixa reta de `backdrop-filter`. Todas carregam máscara em gradiente.

   POR QUE STICKY, E NÃO FIXED. Uma faixa `fixed` cobriria o fim da página
   para sempre: o texto legal do rodapé ficaria desfocado justamente quando
   a pessoa rola até ele para ler. Uma faixa `sticky` com `bottom:0` gruda
   na borda da janela enquanto o trecho que a contém estiver passando, e
   quando o trecho acaba ela PARA no lugar natural dela — e o rodapé, que
   vem depois, entra nítido. Sem uma linha de script medindo o fim da
   página. A condição é estrutural: o .dsf é o ÚLTIMO filho do trecho.

   ALTURA ZERO, CAMADAS PARA FORA. O elemento grudado não ocupa espaço no
   fluxo (block-size:0) e as camadas saem dele para cima, em posição
   absoluta. É o que dispensa a margem negativa que uma faixa de 160px de
   altura real precisaria para não empurrar o conteúdo.

   PARAFUSOS (custom properties do próprio componente, §3):
     --dsf-h       altura da faixa
     --dsf-max     raio da camada mais forte, a da borda
     --dsf-tinta   quanto da cor --dsf-cor pinta a borda (0% = nenhum)
     --dsf-cor     a cor dessa tinta (padrão: --dark)
     --dsf-topo    distância do topo na variação .dsf--topo (a altura de um
                   menu fixo, quando houver)
   ========================================================================== */

.dsf{
  /* A ALTURA É O RESPIRO ENTRE DOBRAS, e não um número próprio. No fim do
     trecho a faixa para no lugar natural dela e fica PARADA sobre os últimos
     --dsf-h pixels da última dobra — para sempre, porque dali a página não
     rola mais. Com a altura igual ao --sec-gap, esses pixels são exatamente
     o padding de baixo da dobra (.sec-pad), onde não há texto. Uma faixa
     mais alta que o respiro deixaria as últimas linhas da última dobra
     desfocadas sem remédio. O token já tem piso e teto (110 a 160px), que é
     também a faixa em que cinco raios cabem sem virar degrau. */
  --dsf-h:var(--sec-gap);
  /* Na borda, o texto de corpo some por completo a partir de ~10px; 14 dá a
     margem para display grande, que resiste mais ao desfoque. */
  --dsf-max:14px;
  --dsf-tinta:0%;
  /* Sentido do gradiente das máscaras: do lado de DENTRO da faixa para a
     borda. Na faixa de baixo o lado de dentro é o de cima. */
  --dsf-dir:to bottom;

  position:sticky;
  inset-block-end:0;
  /* Abaixo do botão flutuante (90) e do menu fixo (100), que precisam ser
     lidos por cima da faixa. A escala da casa está comentada em
     core/tokens.css, junto do --z-overlay. */
  z-index:80;
  display:block;
  block-size:0;
  /* A faixa é decoração sobre conteúdo vivo: sem isto ela engoliria o
     clique em todo link que passasse por baixo dela. */
  pointer-events:none;
}

.dsf::before,
.dsf-camada{
  content:"";
  position:absolute;
  inset-inline:0;
  inset-block-end:0;
  block-size:var(--dsf-h);
}

/* A tinta opcional. Desligada no padrão (--dsf-tinta:0%), e é de propósito:
   a faixa passa sobre seções de tons diferentes, e uma tinta escura sobre
   uma .sec-paper vira mancha. Ligue só quando o trecho inteiro for de um
   tom só. */
.dsf::before{
  z-index:1;
  background:linear-gradient(var(--dsf-dir),
    transparent,
    color-mix(in srgb,var(--dsf-cor,var(--dark)) var(--dsf-tinta),transparent));
}

/* As cinco janelas. Com s = 1/6 da faixa, a camada k vale de (k-1)s a
   (k+2)s: sobe de zero a cheio em uma fatia, fica cheia em outra e volta a
   zero na terceira. A quinta não tem onde voltar a zero e fica cheia até a
   borda — é ela que manda no rodapé da tela. */
.dsf-camada{
  --dsf-s:calc(100% / 6);
  -webkit-backdrop-filter:blur(var(--dsf-b));
  backdrop-filter:blur(var(--dsf-b));
  /* Uma linha por máscara, e não quatro: o #000 aqui é OPACIDADE, não cor,
     e o bin/conformidade.py só reconhece isso na linha que diz mask-image. */
  -webkit-mask-image:linear-gradient(var(--dsf-dir),transparent calc((var(--dsf-k) - 1) * var(--dsf-s)),#000 calc(var(--dsf-k) * var(--dsf-s)),#000 calc((var(--dsf-k) + 1) * var(--dsf-s)),transparent calc((var(--dsf-k) + 2) * var(--dsf-s)));
  mask-image:linear-gradient(var(--dsf-dir),transparent calc((var(--dsf-k) - 1) * var(--dsf-s)),#000 calc(var(--dsf-k) * var(--dsf-s)),#000 calc((var(--dsf-k) + 1) * var(--dsf-s)),transparent calc((var(--dsf-k) + 2) * var(--dsf-s)));
}
/* Cada raio é a metade do seguinte. Progressão geométrica e não linear:
   o olho lê desfoque em escala logarítmica, e 3-6-9-12-14 pareceria
   "nítido, nítido, borrado" — o salto aconteceria todo no começo. */
.dsf-camada:nth-child(1){--dsf-k:1;--dsf-b:calc(var(--dsf-max) * .0625)}
.dsf-camada:nth-child(2){--dsf-k:2;--dsf-b:calc(var(--dsf-max) * .125)}
.dsf-camada:nth-child(3){--dsf-k:3;--dsf-b:calc(var(--dsf-max) * .25)}
.dsf-camada:nth-child(4){--dsf-k:4;--dsf-b:calc(var(--dsf-max) * .5)}
.dsf-camada:nth-child(5){--dsf-k:5;--dsf-b:var(--dsf-max)}

/* ------------------------------------------------- variação: topo */
/* A mesma faixa na borda de CIMA: o .dsf vira o PRIMEIRO filho do trecho e
   gruda no topo. Com menu fixo, --dsf-topo recebe a altura dele, e a faixa
   começa onde o menu termina em vez de passar por baixo dele. */
.dsf--topo{
  --dsf-dir:to top;
  inset-block-end:auto;
  inset-block-start:var(--dsf-topo,0px);
}
.dsf--topo::before,
.dsf--topo .dsf-camada{
  inset-block-end:auto;
  inset-block-start:0;
}

/* ------------------------------------------------- quem não quer */
/* Transparência reduzida é a pessoa pedindo ao sistema menos vidro e menos
   desfoque — exatamente o que esta peça é. Não há versão "menor" dela que
   faça sentido: some. (Chrome 118+; nos outros a consulta não casa e a
   faixa fica, que é o comportamento de antes da consulta existir.) */
@media (prefers-reduced-transparency:reduce){
  .dsf{display:none}
}
/* No papel não há o que rolar, e cinco camadas de backdrop-filter na
   impressão ou viram retângulo cinza ou somem, dependendo do navegador. */
@media print{
  .dsf{display:none}
}

/* ---------------------------------------------------------------- mobile */
/* Na tela do celular 16vh de faixa são ~130px de 844 — mas a coluna de
   texto é estreita e cada linha é curta, então a faixa cobre mais LINHAS do
   que no desktop. Mais baixa e mais branda. */
@media (max-width:640px){
  .dsf{--dsf-h:clamp(64px,11vh,104px);--dsf-max:10px}
}
/* (Acima:) continua menor que o --sec-gap, que tem piso de 110px — a regra
   do fim do trecho segue de pé no celular. */

A borda de baixo da janela deixa de ser um corte reto. O texto e as fotos que chegam a ela vão perdendo o foco nos últimos centímetros, como numa lente de pouca profundidade de campo: nítidos a uma altura de dedo da borda, desfeitos no rodapé da tela. Ninguém repara na peça — repara que a página parece mais cara.

O problema que ela resolve é de acabamento, não de conteúdo: numa página escura e tipográfica, a linha cortada ao meio pela borda da tela é o detalhe que mais denuncia "isto é um navegador". A faixa tira esse corte sem tirar nada da leitura, porque ninguém lê na borda.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
— Não recebe copy. É o último filho do trecho em que vale, com cinco <span> vazios

Uma por borda, no máximo duas por página (embaixo e, se fizer sentido, em cima).

Especificação dos assets

Nenhum.


Como usar

<link rel="stylesheet" href="componentes/efeitos/desfoque-progressivo/estilo.css">

O que se cola é isto, como último filho do elemento que embrulha as dobras — normalmente o <main>:

<main>
  … as dobras da página …
  <div class="dsf" aria-hidden="true">
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
    <span class="dsf-camada"></span>
  </div>
</main>
<footer>… entra nítido …</footer>

Ganchos

Gancho Papel
.dsf a faixa. Último filho do trecho; sticky, altura zero
.dsf-camada × 5 as camadas, vazias. O raio e a janela saem da posição (:nth-child)
--dsf-h altura da faixa. Padrão: --sec-gap (ver Gotchas)
--dsf-max raio da camada da borda. 14px; 10px no celular
--dsf-tinta / --dsf-cor tinta opcional na borda. 0% no padrão
.dsf--topo + --dsf-topo a faixa na borda de cima, recuada da altura do menu

Variações


Gotchas


Movimento reduzido / toque

Nada anda: a faixa só acompanha a rolagem que a pessoa fez, então prefers-reduced-motion não tem o que desligar. Quem tem o que pedir é prefers-reduced-transparency: reduce — a pessoa pedindo ao sistema menos vidro e menos desfoque, que é exatamente o que a peça é. Ali ela some (Chrome 118+). Some também na impressão. Sem backdrop-filter, as camadas não pintam nada e a borda volta a ser o corte reto de sempre.


Histórico

Origem Quando O quê
referência: Neto Scariot 2026-10-01 Clone de referência, reescrito do zero

Referência visual: a faixa de desfoque presa à borda de baixo da janela, em toda a página do portfólio — o componente "progressive blur" do Framer: oito camadas, raio de 0,08 a 10px, janelas de 12,5%, num contêiner fixed de 260px. Da referência veio a técnica das janelas sobrepostas.

Normalizações aplicadas na tradução: cinco camadas em vez de oito; sticky em vez de fixed, para parar no fim do trecho; altura igual ao --sec-gap; raio escrito como fração de um parafuso (--dsf-max) em vez de oito números; aria-hidden e pointer-events:none; prefers-reduced-transparency e @media print. Registro consolidado em projetos/referencias-externas.md.