← Biblioteca componentes/efeitos/selo-circular/ficha.md CódigoTela cheia
efeitos/selo-circular

Selo de texto circular

🟢 estavel complexidade baixa prefixo selo- selo badge texto-curvo svg textpath rotacao css-puro sem-js decoracao marca sem javascript

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

<link rel="stylesheet" href="componentes/efeitos/selo-circular/estilo.css">

markup.html · 82 linhas · 4,0 KB

<!-- ==========================================================================
     SELO DE TEXTO CIRCULAR — selo-
     Prefixo: selo-
     Requer: core (tokens, base, motion.css) + este estilo.css
     SEM JAVASCRIPT. A volta inteira é uma animação CSS.

     O QUE SE CATALOGA AQUI NÃO É A SEÇÃO. É o bloco .selo: um disco de
     texto curvo que gira devagar, com um sinal no miolo. Ele cola sobre
     foto, no canto de uma dobra, ao lado de um título ou preso ao rodapé
     — a <section> abaixo é só o PALCO.

     O TEXTO CURVO É SVG, NÃO CSS. `offset-path` faria a letra andar por
     uma curva, mas cada letra precisaria do próprio elemento e do próprio
     deslocamento, escritos à mão ou por script. O <textPath> resolve em
     um nó e um atributo, e é o que existe desde sempre em todo navegador
     que interessa. A escolha custa uma coisa, e ela está no gotcha do id.

     O ID DO ARCO TEM DE SER ÚNICO NA PÁGINA. O <textPath> aponta para o
     <path> por id (href="#selo-arco"). Dois selos na mesma página com o
     mesmo id fazem os dois textos seguirem o PRIMEIRO arco — e como os
     arcos são idênticos, ninguém percebe até o dia em que um selo tiver
     tamanho diferente do outro. Segundo selo na página: troque o id nos
     dois lugares (selo-arco-2, href="#selo-arco-2").

     O TEXTO TEM DE CABER NA VOLTA, E ISSO SE MEDE. O arco tem 232,5
     unidades de viewBox e a tipografia padrão gasta 4,38 por caractere:
     cabem cerca de 53, contando espaços e bullets. Texto mais longo NÃO
     encolhe nem quebra — o excedente simplesmente não é desenhado, e some
     sem aviso. E não adianta pedir encaixe ao SVG: `textLength` é ignorado
     em <textPath> (medido no Chrome — o texto continuou com 363,5 depois
     de receber textLength="232.5"). A conta é na mão.

     O TEXTO É DECORATIVO E ESTÁ MARCADO COMO TAL. aria-hidden no SVG, e a
     frase real num .visually-hidden ao lado. Motivo: o texto curvo é uma
     repetição em laço com bullets no meio, e leitor de tela leria
     "Patrimônio ponto médio Estratégia ponto médio..." duas vezes, porque
     a frase aparece duplicada para fechar a volta.
     ========================================================================== -->

<section class="sec-dark sec-pad selo-demo">
  <div class="wrap">

    <div class="sec-head">
      <span class="eyebrow" data-rv>Marca</span>
      <h2 class="sec-title ttl">Um selo que <em>não para</em></h2>
      <p class="sec-lead" data-rv data-d="1">Texto curvo girando devagar sobre a foto. CSS puro, sem uma linha de script.</p>
    </div>

    <!-- O palco: uma foto com o selo ancorado no canto. O data-rv fica no
         invólucro, nunca no .selo — o .selo gerencia o próprio transform
         (a rotação), e [data-rv].in{transform:none} venceria por
         especificidade e congelaria o giro para sempre depois da entrada.
         Mesma separação do efeitos/inclinacao-3d. -->
    <div class="selo-palco" data-rv>
      <img class="selo-foto" src="../../../showroom/demo/ph-wide-1.webp" alt="Vista de uma sala de reunião ao amanhecer" loading="lazy" decoding="async">
      <span class="grain" aria-hidden="true"></span>

      <div class="selo" data-selo>
        <svg class="selo-svg" viewBox="0 0 100 100" aria-hidden="true" focusable="false">
          <defs>
            <path id="selo-arco" fill="none"
                  d="M 50,50 m -37,0 a 37,37 0 1,1 74,0 a 37,37 0 1,1 -74,0"></path>
          </defs>
          <text class="selo-texto">
            <textPath href="#selo-arco" startOffset="0">
              Patrimônio · Estratégia · Patrimônio · Estratégia ·
            </textPath>
          </text>
        </svg>

        <span class="selo-miolo" aria-hidden="true">
          <svg viewBox="0 0 24 24" focusable="false">
            <path d="M12 4v14M6 13l6 6 6-6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </span>

        <span class="visually-hidden">Patrimônio e estratégia.</span>
      </div>
    </div>

  </div>
</section>

estilo.css · 102 linhas · 2,5 KB

/* ==========================================================================
   SELO DE TEXTO CIRCULAR — selo-
   Disco de texto curvo girando devagar, com um sinal no miolo.
   CSS puro: nao ha script.js neste componente.
   ========================================================================== */

.selo{
  --selo-size:132px;
  --selo-dur:22s;
  position:relative;
  display:grid;
  place-items:center;
  inline-size:var(--selo-size);
  block-size:var(--selo-size);
  /* Sem isto, o disco encolhe quando o pai e' flex e o espaco aperta --
     e um selo achatado deixa de ser selo. */
  flex:0 0 auto;
}

/* O SVG e' quem gira. Poderia girar o .selo inteiro, mas ai' o miolo giraria
   junto -- e seta girando nao aponta para nada. */
.selo-svg{
  position:absolute;
  inset:0;
  inline-size:100%;
  block-size:100%;
  animation:selo-volta var(--selo-dur) linear infinite;
}

@media (prefers-reduced-motion: reduce){
  /* Colado na regra que desliga, como manda o §6.5. O selo continua
     LEGIVEL parado: o texto curvo e' desenho, nao informacao em
     movimento -- so' o giro sai. */
  .selo-svg{animation:none}
}

.selo-texto{
  font-family:var(--display);
  font-weight:700;
  /* O viewBox e' 100x100, entao a unidade aqui e' porcentagem do disco: o
     corpo acompanha --selo-size sozinho, sem media query nem clamp. */
  font-size:7.4px;
  letter-spacing:.086em;
  text-transform:uppercase;
  fill:var(--fg-2);
}

.selo-miolo{
  position:relative;
  display:grid;
  place-items:center;
  inline-size:34%;
  block-size:34%;
  border-radius:var(--radius-pill);
  border:1px solid var(--rule-2);
  background:color-mix(in srgb, var(--bg) 62%, transparent);
  backdrop-filter:blur(6px);
  color:var(--accent);
}

.selo-miolo svg{
  inline-size:42%;
  stroke:currentColor;
  stroke-width:1.8;
}

@keyframes selo-volta{
  from{transform:rotate(0deg)}
  to{transform:rotate(360deg)}
}

/* ---- Palco de demonstracao (nao e' o componente) ---- */
.selo-palco{
  position:relative;
  overflow:hidden;
  border-radius:var(--radius-media);
  aspect-ratio:16 / 8;
}

.selo-foto{
  inline-size:100%;
  block-size:100%;
  object-fit:cover;
  display:block;
}

/* O selo ancorado no canto da foto. Fora do palco ele nao precisa de
   position: e' um bloco comum, que entra em qualquer fluxo. */
.selo-palco .selo{
  position:absolute;
  inset-block-end:6%;
  inset-inline-end:4%;
}

@media (max-width: 760px){
  .selo{--selo-size:104px}
  .selo-palco{aspect-ratio:4 / 3}
}

@media (max-width: 460px){
  .selo{--selo-size:86px}
}

Um disco de texto curvo em caixa alta que gira devagar, com um sinal parado no miolo. Cola sobre foto, no canto de uma dobra, ao lado de um título ou preso ao rodapé.

O que ele resolve: dar movimento e assinatura a uma dobra parada sem pedir mais conteúdo. Uma foto grande com um selo girando no canto lê como peça de estúdio; a mesma foto sozinha lê como banco de imagem. E custa zero JavaScript — a volta inteira é uma animação CSS.


Quando usar

Quando não usar


Padrão editorial

O texto curvo é rótulo, não frase. Três a cinco palavras separadas por ·, em caixa alta, repetidas duas vezes no markup para fechar a volta sem emenda visível.

O que funciona: valores da marca (Patrimônio · Estratégia · Independência), credencial (Consultoria independente · CVM 2.140), convite (Role para ver · Role para ver).

O que não funciona: frase com sujeito e verbo. Curva quebra a leitura linear, e uma oração dando a volta obriga a pessoa a girar a cabeça para entender — o que ninguém faz. Se precisa ser lido, não é selo.

A repetição é obrigatória. O texto tem de dar a volta inteira. Escrito uma vez só, sobra arco vazio, e o vazio girando parece bug.


Especificação dos assets

O selo não pede asset nenhum — é tipografia e um traço em SVG.

O palco de demonstração usa uma foto paisagem (1600×900 ou maior, recorte 16:8). No uso real, a foto é da dobra onde o selo pousa, não dele.


Como usar

Cole o bloco .selo onde quiser. Fora do palco ele é um bloco comum, que entra em qualquer fluxo:

<div class="selo" data-selo>
  <svg class="selo-svg" viewBox="0 0 100 100" aria-hidden="true" focusable="false">
    <defs>
      <path id="selo-arco" fill="none"
            d="M 50,50 m -37,0 a 37,37 0 1,1 74,0 a 37,37 0 1,1 -74,0"></path>
    </defs>
    <text class="selo-texto">
      <textPath href="#selo-arco" startOffset="0">
        Patrimônio · Estratégia · Patrimônio · Estratégia ·
      </textPath>
    </text>
  </svg>
  <span class="selo-miolo" aria-hidden="true"><!-- ícone --></span>
  <span class="visually-hidden">Sua frase, em prosa.</span>
</div>

Ganchos

Gancho O quê
[data-selo] marcador de documentação. Não há script que o leia
--selo-size diâmetro. 132px no desktop, 104px e 86px nos breakpoints
--selo-dur uma volta completa. 22s
#selo-arco o id do arco que o textPath segue. Único por página — ver gotcha

Variações

Variação Como
Sem miolo apagar .selo-miolo. O disco fica vazado e o que estiver atrás aparece pelo meio — bom sobre foto de textura
Giro ao contrário animation-direction: reverse no .selo-svg. Anti-horário lê como "voltar"
Presa ao canto da janela position: fixed no .selo, fora de qualquer [data-rv]. Vira botão de topo — e aí o miolo pede um <button> de verdade
Mais rápido --selo-dur: 12s. Abaixo de 10s deixa de ser respiração e vira distração

Gotchas


Movimento reduzido / toque

Com prefers-reduced-motion: reduce, o giro para e o selo fica parado e legível — o texto curvo é desenho, não informação em movimento, então não há nada a compensar. A regra que desliga fica colada à que liga, no próprio estilo.css.

No toque não há nada a adaptar: o selo não depende de ponteiro, hover nem gesto.


Histórico

Origem Quando O quê
autoral 2026-09-04 Construído do zero. Padrão de domínio comum, sem referência única

Origem. O selo girando aparece em portfólio de estúdio, em página de moda e em quase todo template premiado de agência — padrão de domínio comum, sem dono. Nada foi copiado: reescrito do zero dentro do contrato desta biblioteca. O registro consolidado vai em projetos/autorais.md.

Decisões da tradução para o contrato. O texto curvo é textPath e não offset-path: offset-path faria a letra andar pela curva, mas exigiria um elemento e um deslocamento por letra, escritos à mão ou por script — e este componente não tem script. Gira o SVG e não o bloco, para o miolo ficar parado. O corpo é porcentagem do viewBox, o que dispensa clamp. O SVG é aria-hidden com a frase real ao lado. E o data-rv mora no invólucro, pela regra de especificidade do §5.

Calibragem no primeiro uso. Espere ajustar --selo-size e a contagem de caracteres do texto: os dois andam juntos, e é a única coisa deste componente que não se resolve por token.