← Biblioteca componentes/hero/letreiro-marca/ficha.md CódigoTela cheia
hero/letreiro-marca

Hero em letreiro de marca

🟢 estavel complexidade média prefixo ltr- hero abertura marca letreiro wordmark tipografia caixa-alta showreel relogio horario institucional

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

<link rel="stylesheet" href="componentes/hero/letreiro-marca/estilo.css">
<script src="componentes/hero/letreiro-marca/script.js"></script>

markup.html · 122 linhas · 6,2 KB

<!-- ==========================================================================
     HERO EM LETREIRO DE MARCA — o nome da casa ocupando a coluna inteira
     Prefixo: ltr-
     Requer: core (tokens, base, motion.css, motion.js) + este estilo.css e
             este script.js
     Compõe: midia/showreel-expansivel (estilo.css e script.js dele) — a capa
             de vídeo sobre o letreiro é a capa do showreel, e o diálogo que
             ela abre é o dele, mais abaixo neste arquivo.

     A IDEIA. A primeira dobra não é uma frase: é o nome. Ele ocupa a largura
     inteira da coluna, em caixa alta e corpo de cartaz, e uma capa de vídeo
     pousa sobre as últimas letras. A promessa vem embaixo, pequena, porque
     quem chegou já leu o nome — e ao lado dela a hora no escritório, que
     diz "isto é um lugar, com gente, agora".

     O LETREIRO É TEXTO, NÃO IMAGEM. A referência monta cada letra com uma
     imagem; aqui é o nome em --display, que troca de fonte com o tema e é
     lido por buscador e por leitor de tela. O corpo sai de uma conta em
     unidade de contêiner: largura da coluna dividida pela largura da
     palavra em "em" (--ltr-largura). O script MEDE essa largura depois que
     a fonte carrega; sem script, vale o número escrito no estilo.css, que
     foi medido para o nome do preview com Manrope.

     QUEM LÊ O QUÊ. O nome existe duas vezes no markup: uma em
     .visually-hidden, que é o que o leitor de tela lê, e outra em
     aria-hidden, que é o que o olho vê e o que o script parte em letras
     para a entrada. Sem isso o leitor soletraria "L, I, B, E…".

     O <h1> É A PROMESSA, não o nome. O nome já está na marca, no <title> e
     no menu; o que a página precisa declarar como assunto é o que a casa
     faz. Mesma escolha da referência.

     A HORA É CONCEDIDA, NÃO ESCRITA. O <time> nasce vazio e [hidden]; o
     script lê o relógio no fuso de data-ltr-fuso (pelo LIB.relogio do core)
     e escreve "17:08", atualizando na virada de cada minuto. Sem script,
     aparece só a cidade — a peça nunca mostra uma hora que não conferiu.
     Minuto, e não segundo: um número piscando a cada segundo no canto da
     primeira dobra é movimento que ninguém pediu.
     ========================================================================== -->

<section class="sec-light ltr" data-ltr>
  <div class="wrap ltr-wrap">

    <div class="ltr-palco">
      <p class="ltr-marca">
        <span class="visually-hidden">Liberta</span>
        <span class="ltr-letras" data-ltr-letras aria-hidden="true">Liberta</span>
      </p>

      <!-- A CAPA DO SHOWREEL, composta. As classes reel-* são do
           midia/showreel-expansivel e vêm da folha dele.
           O data-rv vai no .ltr-midia, que é quem carrega a sombra — com ele
           no .reel-slot de dentro, a sombra aparecia pousada no letreiro um
           segundo antes da capa. E pode morar no mesmo nó do deslocamento
           porque o deslocamento é a propriedade `translate`, e o core zera
           `transform`: são propriedades diferentes. Medido: um [data-rv].in
           com translate computa transform:none e translate intacto. A
           armadilha do §5 é com `transform`, e só com ele. -->
      <div class="ltr-midia" data-rv data-d="2">
        <div class="reel-slot">
          <button class="reel-capa reel-capa--compacta" type="button" data-reel-abrir aria-haspopup="dialog">
            <img class="reel-poster" src="../../../showroom/demo/ph-wide-3.webp"
                 alt="" decoding="async">
            <span class="grain" aria-hidden="true"></span>
            <span class="reel-veu" aria-hidden="true"></span>
            <span class="reel-play" aria-hidden="true">
              <svg viewBox="0 0 24 24" focusable="false">
                <path d="M8 5.5v13l11-6.5z" fill="currentColor"/>
              </svg>
            </span>
            <span class="reel-legenda">
              <span class="reel-titulo">Assistir ao vídeo da casa</span>
              <span class="reel-dur">1 min 30 s</span>
            </span>
          </button>
        </div>
      </div>
    </div>

    <div class="ltr-pe">
      <div class="ltr-promessa">
        <h1 class="ltr-h1" data-rv>Consultoria de investimentos independente para famílias e empresários, no Brasil e fora dele.</h1>
        <a class="ltr-cta" href="#contato" data-rv data-d="1">
          Fale com um sócio
          <svg width="13" height="13" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 7H12M8 3L12 7L8 11"/></svg>
        </a>
      </div>

      <!-- data-ltr-fuso no formato IANA. Duas cidades no mesmo fuso cabem
           numa linha só ("São Paulo · Florianópolis"); em fusos diferentes,
           escreva uma linha .ltr-local por escritório, cada uma com o seu. -->
      <div class="ltr-lugar" data-rv data-d="2">
        <p class="ltr-local" data-ltr-relogio data-ltr-fuso="America/Sao_Paulo">
          <span class="ltr-cidade">São Paulo, SP</span>
          <time class="ltr-hora" data-ltr-hora hidden></time>
        </p>
        <p class="ltr-dica">Role para conhecer</p>
      </div>
    </div>

  </div>
</section>

<!-- O DIÁLOGO DO SHOWREEL — do midia/showreel-expansivel, composto. Mora
     FORA da seção, como último filho do <body>, pela razão escrita na ficha
     dele: dentro da dobra herdaria o escopo de tom, e o vídeo tem fundo
     próprio. O script dele acha a capa e o diálogo pelos data-reel-*. -->
<dialog class="dialog-casca reel-dialog" data-reel-dialog aria-label="Vídeo da casa">
  <div class="reel-quadro" data-reel-quadro>
    <video class="reel-video" data-reel-video controls playsinline preload="none"
           poster="../../../showroom/demo/ph-wide-3.webp">
      <!-- <source src="/midia/institucional.mp4" type="video/mp4"> -->
      <!-- <track kind="captions" src="/midia/institucional.vtt" srclang="pt-BR" label="Português" default> -->
    </video>

    <button class="reel-fechar" type="button" data-reel-fechar aria-label="Fechar o vídeo">
      <svg viewBox="0 0 24 24" focusable="false" aria-hidden="true">
        <path d="M6 6l12 12M18 6L6 18" fill="none" stroke-linecap="round"/>
      </svg>
    </button>
  </div>
</dialog>

estilo.css · 275 linhas · 10,8 KB

/* ==========================================================================
   HERO EM LETREIRO DE MARCA — prefixo ltr-
   O nome da casa ocupando a largura da coluna, com uma capa de vídeo
   pousada sobre as últimas letras e a promessa embaixo.

   Tokens usados:
     --display --sans     o letreiro e a promessa
     --fg --fg-2 --fg-muted  tinta do escopo de tom
     --accent-ink         o filete do link
     --rule               o filete que separa o pé
     --nav-h              o respiro do menu fixo no alto
     --ease               a curva das entradas

   O CORPO DO LETREIRO NÃO É UM NÚMERO, É UMA DIVISÃO:
       font-size = 100cqi / --ltr-largura
   100cqi é a largura do palco (unidade de contêiner); --ltr-largura é a
   largura da palavra medida em "em". O resultado é a palavra exatamente da
   largura da coluna, em qualquer tela, sem media query e sem script
   reescrevendo font-size a cada redimensionamento. O script só MEDE
   --ltr-largura uma vez, depois que a fonte carrega — a palavra e a fonte
   não mudam com a janela, só a coluna muda, e a coluna o CSS já sabe.

   CAIXA ALTA AQUI É DECISÃO, NÃO DESCUIDO. O .display-xl do core diz que
   título vai em caixa baixa e que a caixa alta fica para o texto que é
   EFEITO, acima de 130px — a faixa de marca do rodapé cortina é o
   precedente. Um letreiro de 250px é exatamente esse caso.

   PARAFUSOS (custom properties do próprio componente, §3):
     --ltr-largura     largura da palavra em em (o script mede)
     --ltr-midia-w     largura da capa sobre o letreiro
     --ltr-midia-x     distância da capa à borda direita do palco
     --ltr-midia-desce fração da ALTURA DA CAPA que passa para baixo do
                       letreiro (.3 = um terço dela pendurado embaixo)
     --ltr-passo       intervalo entre uma letra e a seguinte na entrada
   ========================================================================== */

.ltr{
  /* Largura de "LIBERTA" em Manrope 800 com tracking de -.045em, medida no
     Chrome com JavaScript desligado: 3.375em. É o valor SEM script — com
     script, o medido substitui, e ele é outro (3.465em): partir o nome em
     letras desfaz o kerning entre elas, e a palavra partida é 2,7% mais
     larga. Outra palavra ou outra fonte sem script: meça a palavra inteira
     e troque aqui. */
  --ltr-largura:3.375;
  --ltr-midia-w:min(30%,380px);
  --ltr-midia-x:6%;
  --ltr-midia-desce:.3;
  --ltr-passo:55ms;

  display:flex;
  flex-direction:column;
  /* svh e não vh: no celular a barra do navegador recolhe e a dobra não
     pode dar um pulo de 80px quando isso acontece. A primeira linha é o
     fallback de quem não entende svh. */
  min-block-size:100vh;
  min-block-size:100svh;
  padding-block:calc(var(--nav-h) + clamp(8px,2vh,24px)) clamp(22px,4vh,40px);
}

.ltr-wrap{
  flex:1;
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  gap:clamp(36px,7vh,72px);
}

/* ------------------------------------------------------------- o palco */
/* Contêiner de consulta: é a largura DELE que o 100cqi do letreiro lê. */
.ltr-palco{
  position:relative;
  container-type:inline-size;
  /* A capa passa --ltr-midia-desce da própria altura para baixo do
     letreiro, por translate — que não ocupa espaço no fluxo. Este margin é
     a reserva desse pedaço, para a capa não pousar sobre a promessa.
     Altura da capa = largura × 9/16; percentual de margem lê a LARGURA do
     bloco, que é a mesma base do --ltr-midia-w. */
  margin-block-end:calc(var(--ltr-midia-w) * 9 / 16 * var(--ltr-midia-desce));
}

.ltr-marca{
  font-family:var(--display);
  font-weight:800;
  /* Fallback de quem não entende unidade de contêiner (Chrome < 105,
     Safari < 16): perto, não exato. A segunda linha manda em todo o resto. */
  font-size:clamp(64px,22vw,300px);
  font-size:calc(100cqi / var(--ltr-largura));
  /* Caixa alta não tem descendente: .8 fecha a caixa no desenho das letras
     sem cortar acento de maiúscula, que .74 cortava no "É". */
  line-height:.8;
  letter-spacing:-.045em;
  text-transform:uppercase;
  color:var(--fg);
  white-space:nowrap;
}

/* inline-block e não block: é a largura DO TEXTO que o script mede, e um
   bloco mediria a coluna.
   O PADDING DEVOLVE O TRACKING DA ÚLTIMA LETRA. letter-spacing é aplicado
   depois de CADA letra, inclusive a última, e a medida descontava esses
   -.045em de uma área onde há tinta: a conta enchia a coluna com a caixa e
   o "A" passava 7px da borda da coluna a 1440px (medido). Com o padding
   igual ao tracking, a caixa volta a ter a largura do desenho. Mexeu no
   letter-spacing do .ltr-marca, mexa aqui junto. */
.ltr-letras{display:inline-block;padding-inline-end:.045em}

/* ------------------------------------------------- a entrada letra a letra */
/* O script parte o nome em .ltr-letra, cada uma com --i, e põe .is-pronto
   na raiz. Nada entra seco (§5): cada letra sobe, acende e entra em foco.

   POR QUE NÃO O data-split DO CORE: ele parte em PALAVRAS e gera a classe
   .w, que é do core e que componente não estiliza — o mesmo motivo pelo
   qual o hero/centrado-prova-social escalona a própria headline. */
.ltr-letra{display:inline-block}
.ltr.is-pronto .ltr-letra{
  animation:ltrLetra .95s var(--ease) both;
  animation-delay:calc(var(--i) * var(--ltr-passo));
}
@keyframes ltrLetra{
  from{opacity:0;filter:blur(16px);transform:translateY(.22em)}
  to{opacity:1;filter:blur(0);transform:none}
}

/* ANTES DO SCRIPT, O NOME ESPERA ESCONDIDO — mas só quando há script para
   mostrá-lo. Sem esta regra o nome aparecia inteiro no primeiro quadro e
   sumia no seguinte, quando a entrada começava do zero: um pisca na dobra
   mais vista do site. `scripting: enabled` é a consulta que o core já usa
   (motion.css), ao contrário: lá ela devolve a página sem script.

   E UMA REDE: se o script do componente não carregar (motion.js presente,
   este script.js esquecido no consumidor), a animação ltrRede devolve o
   nome em 2s. O .is-pronto desliga a rede — a entrada de verdade assume. */
@media (scripting: enabled){
  .ltr-letras{opacity:0;animation:ltrRede .01s 2s forwards}
  .ltr.is-pronto .ltr-letras{opacity:1;animation:none}
}
@keyframes ltrRede{to{opacity:1}}

/* Movimento reduzido: o nome aparece parado, inteiro, no primeiro quadro.
   O script também não parte as letras — mas a regra de CSS vale sozinha,
   para o caso de a preferência mudar com a página aberta. */
@media (prefers-reduced-motion:reduce){
  .ltr .ltr-letras{opacity:1;animation:none}
  .ltr.is-pronto .ltr-letra{animation:none}
}

/* ------------------------------------------------------------- a capa */
/* Pousada sobre as últimas letras e passando um pouco para baixo delas:
   ancorada na base do letreiro e empurrada para baixo por translate, cujo
   percentual lê a altura da PRÓPRIA capa — é o que faz --ltr-midia-desce
   valer a mesma fração em qualquer tamanho.
   O DESLOCAMENTO É `translate`, NÃO `transform`, e é isso que deixa o
   data-rv da entrada morar neste mesmo nó. O core termina o reveal com
   [data-rv].in{transform:none}, que vale (0,2,0) e apagaria em silêncio um
   transform daqui (contrato §5) — mas `translate` é outra propriedade, e o
   reset não a toca. Medido: [data-rv].in com translate computa
   transform:none e translate intacto. Quem "simplificar" para
   transform:translateY(30%) perde o deslocamento assim que a dobra entra. */
.ltr-midia{
  position:absolute;
  inset-inline-end:var(--ltr-midia-x);
  inset-block-end:0;
  inline-size:var(--ltr-midia-w);
  translate:0 calc(var(--ltr-midia-desce) * 100%);
  z-index:2;
}
/* A sombra separa a capa das letras que ela cobre: sem ela, uma foto
   escura sobre letra escura vira uma mancha só. Mora no invólucro, e não na
   capa: a capa é do midia/showreel-expansivel, e este arquivo só escreve
   classe do próprio prefixo (§6.1). O raio acompanha o da capa.
   A ESCALA da capa — disco menor, legenda de etiqueta — também não é
   daqui: é a variação .reel-capa--compacta do showreel, que nasceu desta
   composição. */
.ltr-midia{
  border-radius:var(--radius-media);
  box-shadow:0 18px 50px -18px color-mix(in srgb,var(--fg) 45%,transparent);
}

/* ------------------------------------------------------------- o pé */
.ltr-pe{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  align-items:end;
  gap:24px 48px;
  padding-block-start:clamp(18px,3vh,28px);
  border-block-start:1px solid var(--rule);
}

.ltr-promessa{display:grid;gap:18px;justify-items:start}

.ltr-h1{
  font-family:var(--display);
  font-weight:600;
  font-size:clamp(17px,1.5vw,22px);
  line-height:1.35;
  letter-spacing:-.01em;
  color:var(--fg);
  max-width:34ch;
}

/* Link de texto, não botão: a capa já é o gesto forte da dobra, e duas
   pílulas no mesmo enquadramento brigariam. O retorno de hover é o filete
   que cresce por ::after — a exceção nomeada do §6, que é tipografia. */
.ltr-cta{
  position:relative;
  display:inline-flex;
  align-items:center;
  gap:10px;
  font-family:var(--display);
  font-weight:700;
  font-size:12px;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--fg);
  padding-block:6px;
}
.ltr-cta::after{
  content:"";
  position:absolute;
  inset-inline:0;
  inset-block-end:0;
  block-size:1px;
  background:var(--accent-ink,var(--accent));
  transform:scaleX(.24);
  transform-origin:left center;
  transition:transform .45s var(--ease);
}
.ltr-cta:hover::after,
.ltr-cta:focus-visible::after{transform:scaleX(1)}
@media (prefers-reduced-motion:reduce){.ltr-cta::after{transition:none}}

.ltr-lugar{
  display:grid;
  gap:6px;
  justify-items:end;
  text-align:end;
}
.ltr-local,
.ltr-dica{
  font-size:11px;
  letter-spacing:.16em;
  text-transform:uppercase;
  line-height:1.4;
}
.ltr-local{color:var(--fg-2)}
.ltr-dica{color:var(--fg-muted)}
/* O <time> nasce [hidden] e só o script o mostra. Quem der display a ele
   desliga o [hidden] à mão (contrato §6.5). */
.ltr-hora{
  display:inline-block;
  margin-inline-start:.6em;
  padding-inline-start:.6em;
  border-inline-start:1px solid var(--rule-2);
  /* Algarismos de largura igual: sem isto "11:11" e "10:08" têm larguras
     diferentes e a linha dança na virada do minuto. */
  font-variant-numeric:tabular-nums;
  color:var(--fg);
}
.ltr-hora[hidden]{display:none}

/* ---------------------------------------------------------------- mobile */
/* Abaixo de 720px a capa não cabe sobre o letreiro sem cobrir metade dele:
   sai da posição absoluta e vira bloco embaixo, na largura da coluna. O
   letreiro continua do tamanho da coluna — a conta é a mesma. */
@media (max-width:720px){
  .ltr{--ltr-midia-desce:0}
  .ltr-midia{
    position:static;
    inline-size:100%;
    translate:none;
    margin-block-start:clamp(20px,4vh,32px);
  }
  .ltr-pe{grid-template-columns:minmax(0,1fr)}
  .ltr-lugar{justify-items:start;text-align:start}
}

script.js · 113 linhas · 5,2 KB

/* ==========================================================================
   HERO EM LETREIRO DE MARCA — ltr-
   Parte da RAIZ de cada instância, nunca do documento (CLAUDE.md §7.6).

   O QUE ESTE SCRIPT FAZ, E É SÓ ISTO:
     1. PARTE o nome em letras para a entrada escalonada e põe .is-pronto
        na raiz. Com movimento reduzido não parte nada: só põe .is-pronto,
        que é o que tira o nome da espera.
     2. MEDE a largura do nome em "em" e a escreve em --ltr-largura. O CSS
        faz o resto (100cqi / largura) em qualquer tamanho de janela — por
        isso a medida é uma só, depois que a fonte carrega, e não a cada
        redimensionamento.
     3. CONCEDE a hora no fuso do escritório, pelo LIB.relogio do core, e a
        atualiza na virada de cada minuto.

   A capa e o vídeo não são daqui: são do midia/showreel-expansivel, que
   esta dobra compõe.
   ========================================================================== */
(function () {
  var raizes = document.querySelectorAll('[data-ltr]');
  if (!raizes.length) return;                    // guard: saída silenciosa

  var reduce = !!(window.LIB && LIB.reduce);

  function dois(n) { return (n < 10 ? '0' : '') + n; }

  [].forEach.call(raizes, function (raiz) {
    var letras = raiz.querySelector('[data-ltr-letras]');
    var marca = letras && letras.parentNode;

    /* ---------- 1. a partição ----------
       O texto do nome vira uma <span> por letra, com o índice em --i. Os
       espaços continuam nós de texto: uma marca de duas palavras quebra a
       contagem do atraso no lugar certo e mantém o espaço com a largura
       da fonte. O leitor de tela não vê nada disto — a cópia partida está
       em aria-hidden, e o nome inteiro em .visually-hidden ao lado. */
    if (letras && !reduce) {
      var texto = letras.textContent.replace(/\s+/g, ' ').trim();
      var i = 0;
      letras.textContent = '';
      for (var k = 0; k < texto.length; k++) {
        var ch = texto.charAt(k);
        if (ch === ' ') { letras.appendChild(document.createTextNode(' ')); continue; }
        var s = document.createElement('span');
        s.className = 'ltr-letra';
        s.style.setProperty('--i', i++);
        s.textContent = ch;
        letras.appendChild(s);
      }
    }
    /* ---------- 2. a medida ----------
       Largura renderizada dividida pelo corpo renderizado: a razão não
       depende do corpo, e é ela que o CSS precisa. O tracking em em escala
       junto, então entra na conta de graça. Medido duas vezes de propósito:
       já, para o caso de a fonte estar em cache, e de novo quando
       document.fonts avisar que carregou — antes disso a medida é da fonte
       de fallback, e uma Arial é bem mais larga que uma Manrope.

       E DEPOIS DE PARTIR, NUNCA ANTES: cada letra numa <span> própria perde
       o kerning com a vizinha, e a palavra partida fica 2,7% mais larga
       (medido: 3,375em inteira, 3,465em partida, Manrope 800). Medida antes
       da partição, a palavra estouraria a coluna em ~29px num palco de 1096. */
    function medir() {
      if (!letras || !marca) return;
      var corpo = parseFloat(getComputedStyle(marca).fontSize);
      var largura = letras.getBoundingClientRect().width;
      if (corpo > 0 && largura > 0) {
        raiz.style.setProperty('--ltr-largura', (largura / corpo).toFixed(4));
      }
    }
    medir();
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(medir);

    /* Um quadro de espera: a classe que dispara a animação precisa chegar
       DEPOIS de as letras existirem na árvore, senão o navegador pode
       agrupar as duas escritas e as letras nascerem já no estado final. */
    requestAnimationFrame(function () { raiz.classList.add('is-pronto'); });

    /* ---------- 3. a hora ---------- */
    var relogio = raiz.querySelectorAll('[data-ltr-relogio]');
    if (!relogio.length || !window.LIB || !LIB.relogio) return;

    function pintar() {
      [].forEach.call(relogio, function (linha) {
        var alvo = linha.querySelector('[data-ltr-hora]');
        if (!alvo) return;
        var agora = LIB.relogio(linha.getAttribute('data-ltr-fuso') || 'America/Sao_Paulo');
        if (!agora) { alvo.hidden = true; return; }   // fuso ilegível: nenhuma hora
        var hora = dois(agora.h) + ':' + dois(agora.m);
        alvo.textContent = hora;
        alvo.setAttribute('datetime', hora);
        alvo.hidden = false;
      });
    }

    /* Na VIRADA do minuto, e não a cada 60s a partir de agora: um
       setInterval de 60s iniciado às 17:08:50 mostraria 17:08 até 17:09:50,
       quase um minuto errado. Todo fuso tem offset de minutos inteiros, então
       a virada de minuto em UTC é a virada em qualquer fuso. Os 40ms de
       folga evitam acordar 1ms antes da virada e pintar o mesmo minuto. */
    function agendar() {
      setTimeout(function () { pintar(); agendar(); }, 60000 - (Date.now() % 60000) + 40);
    }
    pintar();
    agendar();

    /* Aba em segundo plano estrangula o setTimeout; quem volta à aba vê a
       hora certa na hora, sem esperar o próximo disparo. */
    document.addEventListener('visibilitychange', function () {
      if (!document.hidden) pintar();
    });
  });
})();

A primeira dobra não é uma frase: é o nome. A marca, em caixa alta e corpo de cartaz, ocupa a largura exata da coluna, e a capa do vídeo institucional pousa sobre as últimas letras, pendurada um terço para baixo delas. Embaixo, separada por um filete, vem a promessa em corpo pequeno, um link de texto e — do outro lado — a cidade do escritório com a hora de lá agora.

O problema editorial que ela resolve: o hero de assessoria quase sempre abre com uma frase que serve a qualquer casa ("Seu patrimônio em boas mãos"). Aqui a primeira coisa lida é o nome, e a promessa vem depois, já sabendo de quem é. A hora local diz, sem dizer, que existe um escritório com gente dentro.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Nome 4 a 10 caracteres, escrito duas vezes: na .visually-hidden e na [data-ltr-letras]. As duas iguais
Promessa (<h1>) 8 a 18 palavras: o que a casa faz e para quem. Sem repetir o nome
Link 2 a 5 palavras, verbo e pessoa — "Fale com um sócio"
Cidade a do escritório cujo fuso está em data-ltr-fuso
Dica de rolagem opcional, 2 a 4 palavras
Título da capa 3 a 6 palavras; a duração ao lado tem de bater com o arquivo

O que mantém a peça: o nome é o único elemento grande. A promessa fica em corpo de leitura (17–22px) de propósito; subir esse corpo cria dois protagonistas na mesma dobra.

Especificação dos assets


Como usar

<link rel="stylesheet" href="componentes/midia/showreel-expansivel/estilo.css">
<link rel="stylesheet" href="componentes/hero/letreiro-marca/estilo.css">
<script src="componentes/midia/showreel-expansivel/script.js"></script>
<script src="componentes/hero/letreiro-marca/script.js"></script>

O <dialog> do showreel vem no markup.html, depois da seção, e vai como último filho do <body>.

Ganchos

Gancho Papel
[data-ltr] a seção
[data-ltr-letras] a cópia aria-hidden do nome, que o script parte em letras
[data-ltr-relogio] + data-ltr-fuso uma linha por escritório; fuso IANA (America/Sao_Paulo)
[data-ltr-hora] o <time>, vazio e [hidden] no markup
.is-pronto posto pelo script na raiz; dispara a entrada
--ltr-largura largura do nome em em. O script mede; o CSS traz o valor sem script
--ltr-midia-w / --ltr-midia-x / --ltr-midia-desce largura da capa, recuo da borda direita e quanto dela pende abaixo do letreiro
--ltr-passo intervalo entre letras na entrada. 55ms

Variações


Compliance


Gotchas


Movimento reduzido / toque

Com reduce o script não parte o nome: ele aparece inteiro e parado no primeiro quadro, e a medida é a da palavra inteira (3,375em). A capa e o link continuam funcionando. Nada depende de hover: o retorno do link é o filete por ::after, que no toque fica no repouso (um quarto da largura) e não esconde nada.


Histórico

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

Referência visual: o hero da home — o letreiro SKAVE na largura da tela, a capa de vídeo sobre as letras "VE", a promessa pequena embaixo e a linha "São José/SC — São Paulo/SP — 05:08:29PM".

Normalizações aplicadas na tradução: o letreiro era cinco imagens, uma por letra, e virou texto em --display com corpo por unidade de contêiner; a capa virou a do midia/showreel-expansivel, composta, com a variação .reel-capa--compacta que nasceu aqui; a hora perdeu os segundos e passou a ser concedida pelo LIB.relogio — que foi promovido ao core por causa desta peça, a terceira cópia da função; a entrada por máscara virou subida com blur, letra a letra; Next.js, Tailwind e Lenis não atravessaram. Registro consolidado em projetos/referencias-externas.md.