<!-- ==========================================================================
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.
========================================================================== -->
<sectionclass="sec-light ltr"data-ltr>
<divclass="wrap ltr-wrap">
<divclass="ltr-palco">
<pclass="ltr-marca">
<spanclass="visually-hidden">Liberta</span>
<spanclass="ltr-letras"data-ltr-letrasaria-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. -->
<divclass="ltr-midia"data-rvdata-d="2">
<divclass="reel-slot">
<buttonclass="reel-capa reel-capa--compacta"type="button"data-reel-abriraria-haspopup="dialog">
<imgclass="reel-poster"src="../../../showroom/demo/ph-wide-3.webp"alt=""decoding="async">
<spanclass="grain"aria-hidden="true"></span>
<spanclass="reel-veu"aria-hidden="true"></span>
<spanclass="reel-play"aria-hidden="true">
<svgviewBox="0 0 24 24"focusable="false">
<pathd="M8 5.5v13l11-6.5z"fill="currentColor"/>
</svg>
</span>
<spanclass="reel-legenda">
<spanclass="reel-titulo">Assistir ao vídeo da casa</span>
<spanclass="reel-dur">1 min 30 s</span>
</span>
</button>
</div>
</div>
</div>
<divclass="ltr-pe">
<divclass="ltr-promessa">
<h1class="ltr-h1"data-rv>Consultoria de investimentos independente para famílias e empresários, no Brasil e fora dele.</h1>
<aclass="ltr-cta"href="#contato"data-rvdata-d="1">
Fale com um sócio
<svgwidth="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"><pathd="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. -->
<divclass="ltr-lugar"data-rvdata-d="2">
<pclass="ltr-local"data-ltr-relogiodata-ltr-fuso="America/Sao_Paulo">
<spanclass="ltr-cidade">São Paulo, SP</span>
<timeclass="ltr-hora"data-ltr-horahidden></time>
</p>
<pclass="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-*. -->
<dialogclass="dialog-casca reel-dialog"data-reel-dialogaria-label="Vídeo da casa">
<divclass="reel-quadro"data-reel-quadro>
<videoclass="reel-video"data-reel-videocontrolsplaysinlinepreload="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>
<buttonclass="reel-fechar"type="button"data-reel-fechararia-label="Fechar o vídeo">
<svgviewBox="0 0 24 24"focusable="false"aria-hidden="true">
<pathd="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 .95svar(--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 .01s2s 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:0calc(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:018px50px-18pxcolor-mix(in srgb,var(--fg) 45%,transparent);
}
/* ------------------------------------------------------------- o pé */.ltr-pe{
display:grid;
grid-template-columns:minmax(0,1fr) auto;
align-items:end;
gap:24px48px;
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 .45svar(--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 silenciosavar 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 horavar 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
Marca com nome curto — 4 a 10 caracteres —, que aguenta virar cartaz: Liberta, Tarvos, uma sigla.
Home institucional de casa que quer ser reconhecida pelo nome antes do argumento.
Quando existe vídeo institucional (ou um pôster forte) para a capa. Sem ele, ver a variação sem capa.
Quando não usar
Nome longo ou de duas palavras grandes ("Gabriel Rocha Estúdio"): o corpo cai abaixo do de cartaz e a peça vira um título comum. Use a sigla, ou hero/full-bleed-media.
Página de campanha em que a promessa é o que converte — ali a frase precisa ser grande: hero/full-bleed-media ou hero/centrado-prova-social.
Página interna: o letreiro é a assinatura da home. Repetido em cada página, vira logo gigante.
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
Pôster 16:9, mínimo 1600×900, até 250 KB, com o assunto no terço esquerdo e central: o terço de baixo recebe a legenda e o centro recebe o disco de play.
Vídeo (opcional) mp4 H.264, com legenda .vtt — é o do midia/showreel-expansivel, e a especificação completa está na ficha dele.
O pôster não pode ter texto: ele concorre com o letreiro, que está a 200px de distância.
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
Escopo escuro — .sec-dark no lugar de .sec-light. Nada mais muda.
Sem capa — apagar o .ltr-midia e o <dialog>, não compor o showreel, --ltr-midia-desce:0.
Dois escritórios — uma .ltr-local por escritório, cada uma com o próprio fuso.
Capa à esquerda — hoje pede trocar inset-inline-end por inset-inline-start no estilo; vira parafuso no primeiro projeto que pedir.
Compliance
A hora é concedida, não escrita. O <time> nasce vazio e [hidden], e só aparece quando o script consegue ler o relógio no fuso declarado (pelo LIB.relogio do core). Sem JavaScript, ou com fuso ilegível, aparece só a cidade.
A cidade tem de ser a do fuso. "São Paulo" com data-ltr-fuso="Europe/Lisbon" mostra uma hora falsa com cara de verdadeira.
A duração na capa é informação, e tem de bater com o vídeo publicado.
Gotchas
O corpo é uma divisão, não um número.font-size: calc(100cqi / var(--ltr-largura)): largura do palco dividida pela largura da palavra em em. O script mede a largura uma vez, depois que a fonte carrega, e o CSS resolve qualquer tamanho de janela sozinho — sem ResizeObserver e sem reescrever font-size. Medido a 1440px: borda direita das letras em 1261, borda do palco em 1261.
Partir em letras desfaz o kerning. Cada letra numa <span> perde o par com a vizinha, e "LIBERTA" partida é 2,7% mais larga (3,465em contra 3,375em). Por isso o script mede depois de partir; medida antes, a palavra estouraria a coluna em ~29px. E por isso o valor sem script no CSS (3,375) é diferente do que o script escreve.
O padding devolve o tracking da última letra.letter-spacing entra depois de cada letra, inclusive a última, e a medida descontava -.045em de uma área com tinta: o "A" passava 7px da coluna. padding-inline-end:.045em no .ltr-letras fecha a conta. Mexeu no tracking, mexa no padding.
O nome existe duas vezes no markup. Uma em .visually-hidden (o leitor de tela lê "Liberta") e outra em aria-hidden (o olho vê, o script parte). Sem isso, o leitor soletraria.
O <h1> é a promessa. O nome está na marca, no <title> e no menu; o assunto da página é o que a casa faz.
Antes do script, o nome espera escondido — só quando há script (@media (scripting: enabled)). Sem isso ele aparecia inteiro no primeiro quadro e sumia no seguinte, quando a entrada começava. E há uma rede: se o script.js do componente não for colado, uma animação de CSS devolve o nome em 2s.
data-rv e translate no mesmo nó, de propósito. A capa é deslocada por translate, e o core zera transform: propriedades diferentes. Medido: [data-rv].in com translate computa transform:none e o translate intacto. Quem trocar por transform: translateY(30%) perde o deslocamento quando a dobra entra (contrato §5). E o data-rv precisa estar ali: no .reel-slot de dentro, a sombra do invólucro aparecia pousada no letreiro um segundo antes da capa.
A capa é do showreel, e a escala dela também. O disco de 84px e a legenda de 1.3rem do showreel foram desenhados para a largura da coluna; numa capa de 330px o título encostava no disco. A saída foi a variação .reel-capa--compactano showreel, e não um ajuste daqui: este arquivo só escreve classe ltr- (§6.1).
Minuto, não segundo. A referência mostra segundos. Um número que muda a cada segundo no canto da primeira dobra é movimento automático, e pediria pausa (WCAG 2.2.2). A hora vira na virada do minuto, não 60s depois de a página abrir: um setInterval iniciado às 17:08:50 mostraria 17:08 até 17:09:50.
Caixa alta é decisão. O .display-xl do core reserva a caixa alta para texto que é efeito, acima de 130px — o precedente é a faixa de marca do rodape/cortina-fixa. Este letreiro é esse caso.
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.
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.