Uma capa com foto, botão de play e legenda. No clique ela cresce do lugar exato onde está até cobrir a tela, e o que aparece lá dentro é um <video> com os controles nativos do navegador.
O que ele resolve: colocar vídeo numa página institucional sem que o vídeo domine a dobra antes de alguém pedir. A capa é uma foto — pesa o que pesa uma foto — e o arquivo de vídeo só começa a existir quando a pessoa clica.
Quando usar
- Showreel, vídeo institucional, depoimento em vídeo numa página que não é sobre vídeo.
- Case com filme, onde o vídeo é a prova e o texto é o argumento.
- Sempre que o vídeo for opcional para entender a página. Se for obrigatório, ele não devia estar atrás de um clique.
Quando não usar
- Quando o detalhe é texto, foto e CTA — aí é o
overlay/card-expansivel-fullscreen, que já carrega tudo nosdata-*do próprio card. Ver a comparação abaixo. - Vários vídeos numa grade. Este componente é de um vídeo só, e a expansão a partir da posição exata pressupõe uma capa. Para uma fileira de vídeos, o irmão resolve melhor.
- Vídeo de fundo, sem som e em laço — isso não é showreel, é textura. Vai direto na dobra, sem capa e sem diálogo.
Qual dos dois irmãos
midia/showreel-expansivel |
overlay/card-expansivel-fullscreen |
|
|---|---|---|
| Conteúdo do detalhe | vídeo | texto, foto, tags e CTA |
| Quantidade | um | um trilho deles |
| Diálogo | <dialog> nativo |
<div role="dialog"> |
| Por que | barra, volume, velocidade, legenda, tela cheia e picture-in-picture não se reimplementam bem | o conteúdo mora nos data-* e não há HTML duplicado |
Padrão editorial
Na capa cabem duas informações e nada mais: o que é o vídeo e quanto tempo ele dura.
A duração não é enfeite — é a informação que decide o clique. "Assistir ao showreel" sem duração é um convite para o desconhecido; com "1 min 30 s" vira uma decisão que a pessoa consegue tomar.
O título é imperativo curto: Assistir ao showreel, Ver o filme do projeto, Conheça a casa em 90 segundos. Não repita o título da dobra — a capa fica logo abaixo dele, e ler a mesma frase duas vezes com dois pesos diferentes parece erro de montagem.
A foto da capa é um frame do próprio vídeo, e de preferência não o primeiro. O primeiro frame costuma ser preto ou uma vinheta.
Especificação dos assets
| Asset | Especificação |
|---|---|
| Vídeo | mp4 com H.264, 1920×1080. Um webm adicional é bem-vindo mas não obrigatório |
| Poster | 1600×900 ou maior, um frame do próprio vídeo. É o que segura a dobra até o clique |
| Legenda | um .vtt em <track kind="captions">. Não é opcional se há fala |
Neste preview o <video> não traz <source>. A biblioteca não versiona vídeo em showroom/demo/, então o que se vê é o poster real e os controles nativos, sem arquivo por trás. A expansão, o diálogo, o foco, o Esc e o fechamento funcionam e podem ser conferidos; só a reprodução não. No uso do consumidor, descomente o <source>.
Como usar
Cole a <section> e, fora dela, o <dialog> — como último filho do <body>. Dentro de uma dobra ele herdaria o escopo de tom dela, e o vídeo tem fundo próprio.
O <dialog> leva duas classes, na ordem class="dialog-casca reel-dialog". A primeira é a primitiva do core (base.css, já na lista de dependências): ela zera padding, borda, fundo e os limites de tamanho que o navegador dá de graça ao <dialog>, e pinta o ::backdrop. A segunda é a forma desta peça — o palco de tela cheia, o display de [open] e o grau do véu.
Depois: troque o poster, descomente o <source> e o <track>, e ajuste título e duração.
Ganchos
| Gancho | O quê |
|---|---|
[data-reel-abrir] |
o <button> da capa |
[data-reel-dialog] |
o <dialog> |
[data-reel-quadro] |
a caixa que cresce. Recebe as três custom properties |
[data-reel-video] |
o <video>, para tocar ao abrir e pausar ao fechar |
[data-reel-fechar] |
o botão de fechar |
--reel-dx / --reel-dy / --reel-s |
a diferença entre a capa e o quadro, escritas pelo script |
--reel-play-size |
diâmetro do disco de play. 84px, 64px no mobile |
--reel-veil-mix |
grau do véu do ::backdrop. 88% — mais escuro que os 78% da casa, e de propósito |
.is-abrindo |
dispara a transição para a identidade |
Variações
| Variação | Como |
|---|---|
| Sem legenda na capa | apagar .reel-legenda |
| Capa em retrato | aspect-ratio: 3/4 na capa e 9/16 no quadro. O object-fit:contain do vídeo cuida do resto |
| Vídeo hospedado fora | trocar o <video> por <iframe>. Perde-se a pausa ao fechar, e a CSP do site precisa liberar o frame |
| Play maior | --reel-play-size: 104px |
| Capa compacta | .reel-capa--compacta no <button>. Disco de 46px e legenda em corpo de etiqueta, para capa de 240 a 400px — com a escala padrão o título encostava no disco. Nasceu na composição com o hero/letreiro-marca (2026-10-01) |
Gotchas
-
displaysó em.reel-dialog[open]. O<dialog>nascedisplay:nonena folha do agente do usuário, e qualquer regra de autor comdisplayvence essa — escrever.reel-dialog{display:grid}deixaria o diálogo visível o tempo todo, com a página rolando por baixo. É a mesma armadilha do §6.5 do contrato, na versão do elemento nativo. -
showModal(), nãoshow(). ÉshowModalque traz o foco preso, o::backdrope a página inerte. Comshow()o diálogo abre como caixa comum e a página continua navegável por trás — o que parece funcionar até alguém usarTab. -
Dois
requestAnimationFrame, não um. O FLIP escreve o transform inicial e depois o remove. Com uma única espera, o navegador às vezes agrupa as duas escritas e a transição não acontece: o quadro simplesmente aparece no lugar final. Dois quadros forçam a separação. -
O
play()vai dentro decatch. O play é consequência de um clique, então o navegador costuma permitir — "costuma". Política de autoplay muda por navegador e por configuração, e uma promessa rejeitada semcatchvira erro vermelho no console de quem só abriu o vídeo. -
O clique no fundo é a única coisa que falta ao
<dialog>. Ele não fecha sozinho ao clique fora. A verificação ée.target === dlg: se o alvo é o próprio diálogo, o clique caiu fora do quadro — dentro, o alvo seria o quadro ou o vídeo. -
Pausa num lugar só. O evento
closedispara tanto pelo botão quanto peloEsc. Tratar os dois caminhos separadamente é o jeito de um deles esquecer de pausar o vídeo — e vídeo tocando atrás de um diálogo fechado é um som sem origem visível. -
O véu é mais escuro que o padrão da casa, e isso é desenho. O
--veildo core mistura--darka 78%; aqui o diálogo redeclara o mesmo nome a 88%, via--reel-veil-mix. Vídeo pede sala escura. O número: com uma dobra.sec-paperpor trás, o composto vai dergb(64,64,64)a 78% parargb(40,40,40)a 88% — 11 pontos deL*em CIELAB, umas dez vezes o limiar do perceptível. Sobre dobra escura os dois graus dão o mesmo pixel: o grau do véu só se vê quando há claro atrás. Quem quiser o padrão da casa apaga a linha; quem quiser outro grau troca--reel-veil-mix. -
--veilé declarado no<dialog>, não no::backdrop. O::backdropherda custom property do elemento que o originou, então declarar--veilna classe do diálogo basta para a primitiva do core pintar o véu certo. Escrever uma regra.reel-dialog::backdropsó para trocar a cor traria de volta a duplicação que a primitiva veio resolver. -
overflow:hiddenfica local, contra ovisibleda casca. O diálogo é o palco: com um quadro maior que a janela — a variação de vídeo em retrato, ou um--radius-mediagrande — o que sobra fica dentro da tela em vez de pintar por fora dela. Medido: em1440×900,1440×420e390×844o quadro cabe no palco e os dois valores dariam o mesmo pixel; a linha é a garantia para quem mexer nas medidas. -
line-height:0na capa não é firula. O<button>alinha o conteúdo como texto, e sem isso a foto ganha um filete de descida embaixo. -
O triângulo do play tem
margin-inline-start:8%. Centrado pelo cálculo ele parece deslocado para a esquerda: o centro óptico de um triângulo não é o centro geométrico.
Movimento reduzido / toque
Com prefers-reduced-motion: reduce, o diálogo continua abrindo — ele foi pedido. Some só a coreografia: o quadro aparece no lugar final sem crescer, o script não mede nem escreve transform, e a capa para de escalar no hover.
No toque, a capa é um <button> e responde ao toque como qualquer botão. Os controles do vídeo são os nativos, que já são os certos para tela sensível — inclusive o botão de tela cheia, que no celular é o que a pessoa realmente vai usar.
Por teclado: Tab chega à capa, Enter ou Espaço abre, o foco entra no diálogo e fica preso lá, Esc fecha e o foco volta para a capa. Nada disso foi escrito aqui — é tudo do <dialog>.
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. A capa de vídeo que cresce até a tela aparece em site de produtora, de estúdio e em página institucional com showreel — 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.
Por que existe ao lado do irmão. O overlay/card-expansivel-fullscreen já faz a mesma expansão a partir do lugar exato do card. A divisão é pelo conteúdo do detalhe: texto, foto e CTA são dele; vídeo é deste. E a razão é específica — barra, volume, velocidade, legenda, tela cheia e picture-in-picture não se reimplementam bem, então o detalhe de vídeo tem de entregar o player nativo.
Decisões da tradução para o contrato. O <dialog> nativo com showModal, pelo mesmo critério do <input type="range"> no midia/comparador-antes-depois: usar o elemento nativo em vez de reimplementar o comportamento dele. A expansão é FLIP, com dois quadros de espera. O play() vai dentro de catch. E o display mora em [open].
2026-09-06 — a casca do <dialog> virou primitiva. Os quatro componentes com <dialog> da biblioteca escreviam a mesma zeragem de caixa e o mesmo véu. Isso subiu para .dialog-casca no core/base.css, e o véu para o token --veil. Saíram deste arquivo padding, border, background, os dois max-* e a regra ::backdrop inteira; ficaram o palco de tela cheia, o display de [open], o overflow:hidden e o grau do véu. Medido antes e depois com bin/medir.js: a caixa do diálogo (1425×900), a do quadro, o ::backdrop (0.88 de alfa, blur(6px)) e o FLIP (--reel-s de 0.8097, quadro nascendo em [164.5, 290, 1096, 616.5], exatamente sobre a capa) saem idênticos. Mudaram três computados, todos previstos: max-width e max-height de 100% para none — a primitiva remove o limite em vez de neutralizá-lo — e o color do diálogo, de rgb(0,0,0) (o CanvasText do navegador) para o --fg herdado da dobra, porque a primitiva declara color:inherit. O color não pinta nada aqui: o screenshot do diálogo aberto com um valor e com o outro sai byte a byte igual (mesmo MD5).
Calibragem no primeiro uso. Espere ajustar a duração da transição de abertura (.58s) contra o tempo real de carregamento do vídeo: com arquivo pesado, a coreografia termina antes de o primeiro frame existir, e o quadro fica preto por um instante. Um poster bom no <video> — o mesmo da capa — cobre essa janela.