← Biblioteca componentes/midia/showreel-expansivel/ficha.md CódigoTela cheia
midia/showreel-expansivel

Showreel expansível

🟢 estavel complexidade média prefixo reel- video showreel capa dialogo dialog fullscreen expansivel flip controles-nativos acessibilidade

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

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

markup.html · 90 linhas · 4,2 KB

<!-- ==========================================================================
     SHOWREEL EXPANSÍVEL — capa que cresce até a tela e entrega o vídeo
     Prefixo: reel-
     Requer: core (tokens, base, motion.css, motion.js) + este estilo.css e
             este script.js

     Uma capa com foto e botão de play. 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 DIÁLOGO É UM <dialog> NATIVO, e essa é a diferença para o irmão
     overlay/card-expansivel-fullscreen, que usa um <div role="dialog">
     com hidden. Com o elemento nativo vêm prontos: prender o foco dentro
     do diálogo, fechar no Esc, devolver o foco ao botão que abriu, e
     tornar o resto da página inerte para tecnologia assistiva. São quatro
     comportamentos que o irmão precisa escrever à mão — ele nasceu antes,
     e a troca lá custaria refazer a coreografia dele.

     QUANDO USAR QUAL: se o conteúdo do detalhe é texto, foto e CTA, é o
     overlay/card-expansivel-fullscreen, que já carrega tudo nos data-* do
     próprio card. Este aqui existe para VÍDEO, e por um motivo específico:
     vídeo pede os controles nativos — barra, volume, velocidade, legenda,
     tela cheia, picture-in-picture —, e nenhum deles se reimplementa bem.

     O <video> NÃO TRAZ <source> NESTE PREVIEW. A biblioteca não versiona
     vídeo em showroom/demo/, e a capa é um poster real. No uso do
     consumidor, descomente o <source> e aponte para o arquivo — ver a
     especificação de assets na ficha.

     A CAPA É UM <button>, não um <div> com onclick. Teclado, Enter, Espaço
     e o anúncio de "botão" vêm de graça.
     ========================================================================== -->

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

    <div class="sec-head">
      <span class="eyebrow" data-rv>Showreel</span>
      <h2 class="sec-title ttl">A casa em <em>90 segundos</em></h2>
      <p class="sec-lead" data-rv data-d="1">Clique na capa. Ela cresce até a tela e entrega o vídeo com os controles do seu navegador.</p>
    </div>

    <!-- data-rv no invólucro: o script mede a posição da capa para fazê-la
         crescer do lugar exato, e escreve transform no quadro do diálogo.
         Manter a entrada num nó separado é a regra do §5. -->
    <div class="reel-slot" data-rv data-d="2">
      <button class="reel-capa" type="button" data-reel-abrir aria-haspopup="dialog">
        <img class="reel-poster" src="../../../showroom/demo/ph-wide-4.webp"
             alt="" loading="lazy" 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 showreel</span>
          <span class="reel-dur">1 min 30 s</span>
        </span>
      </button>
    </div>

  </div>
</section>

<!-- O diálogo mora FORA da seção, como último filho do <body>. Dentro de uma
     dobra ele herdaria o escopo de tom dela, e o vídeo tem fundo próprio.

     .dialog-casca é a primitiva do core (base.css, já exigido acima): ela
     zera a caixa que o navegador dá ao <dialog> e pinta o ::backdrop. A
     classe do core vem ANTES da local. -->
<dialog class="dialog-casca reel-dialog" data-reel-dialog aria-label="Showreel">
  <div class="reel-quadro" data-reel-quadro>
    <video class="reel-video" data-reel-video controls playsinline preload="none"
           poster="../../../showroom/demo/ph-wide-4.webp">
      <!-- <source src="/midia/showreel.mp4" type="video/mp4"> -->
      <!-- <track kind="captions" src="/midia/showreel.vtt" srclang="pt-BR" label="Português" default> -->
    </video>

    <button class="reel-fechar" type="button" data-reel-fechar aria-label="Fechar o showreel">
      <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 · 238 linhas · 7,8 KB

/* ==========================================================================
   SHOWREEL EXPANSIVEL — reel-
   Capa que cresce ate a tela e entrega o video com controles nativos.
   O script escreve --reel-dx, --reel-dy e --reel-s no quadro do dialogo.
   ========================================================================== */

.reel-capa{
  position:relative;
  display:block;
  inline-size:100%;
  padding:0;
  border:0;
  overflow:hidden;
  border-radius:var(--radius-media);
  aspect-ratio:16 / 9;
  background:var(--dark-2);
  cursor:pointer;
  /* O botao vira caixa de imagem: sem isto o user-agent alinha o conteudo
     como texto e a foto ganha um filete de descida embaixo. */
  line-height:0;
  color:var(--light);
}

.reel-poster{
  inline-size:100%;
  block-size:100%;
  object-fit:cover;
  display:block;
  transition:filter .5s var(--ease);
}

/* HOVER NÃO MEXE NA IMAGEM (CLAUDE.md §6, regra de 2026-09-29). O zoom de
   foto sob o ponteiro saiu: escala e deslocamento de mídia no hover mexem
   em recorte e composição, e é o que quebra a página quando o componente
   é colado noutro lugar. O retorno ficou em cor e brilho, que não mudam
   a geometria de nada. */
.reel-capa:hover .reel-poster,
.reel-capa:focus-visible .reel-poster{
  filter:brightness(.82);
}

/* Veu mascarado em gradiente, como manda o §6: caixa reta com
   backdrop-filter produz borda dura e le "chapado". */
.reel-veu{
  position:absolute;
  inset-block-end:0;
  inset-inline:0;
  block-size:58%;
  background:linear-gradient(to top, color-mix(in srgb, var(--dark) 88%, transparent), transparent);
  mask-image:linear-gradient(to top,#000 0%,rgba(0,0,0,.9) 44%,transparent 100%);
  pointer-events:none;
}

.reel-play{
  position:absolute;
  inset-block-start:50%;
  inset-inline-start:50%;
  transform:translate(-50%,-50%);
  display:grid;
  place-items:center;
  inline-size:var(--reel-play-size, 84px);
  block-size:var(--reel-play-size, 84px);
  border-radius:var(--radius-pill);
  border:1px solid color-mix(in srgb, var(--light) 45%, transparent);
  background:color-mix(in srgb, var(--dark) 30%, transparent);
  backdrop-filter:blur(10px);
  transition:transform .5s var(--ease), background-color .5s var(--ease);
}

/* Sem salto no hover (CLAUDE.md §6, 2026-09-29): o disco acende e não
   cresce, a mesma regra do .btn do core — botão não salta nem move. */
.reel-capa:hover .reel-play,
.reel-capa:focus-visible .reel-play{
  background:color-mix(in srgb, var(--accent) 24%, transparent);
}

.reel-play svg{
  inline-size:34%;
  /* O triangulo de play e' otico, nao geometrico: centrado pelo calculo ele
     parece deslocado para a esquerda. */
  margin-inline-start:8%;
}

.reel-legenda{
  position:absolute;
  inset-block-end:0;
  inset-inline:0;
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:16px;
  padding:26px 28px;
  line-height:1.3;
  text-align:start;
}

.reel-titulo{
  font-family:var(--display);
  font-weight:700;
  font-size:clamp(1.05rem, 1.6vw, 1.3rem);
  color:var(--light);
}

.reel-dur{
  font-size:.82rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--light) 68%, transparent);
}

/* ---- O dialogo ----
   A casca vem da primitiva .dialog-casca do core, declarada ANTES desta
   classe no markup: e' ela que zera padding, borda, fundo e os limites de
   tamanho que o navegador da' de graca ao <dialog>, e que pinta o
   ::backdrop com --veil. Aqui fica so' o que e' FORMA desta peca.

   O <dialog> nasce display:none na folha do agente do usuario e so' e'
   exibido com [open]. Escrever `.reel-dialog{display:grid}` sem o [open]
   deixaria o dialogo VISIVEL o tempo todo, com a pagina rolando por baixo:
   e' a mesma armadilha do §6.5, na versao do elemento nativo. A primitiva
   nao cobre este display de proposito -- cada dialogo da casa entra de um
   jeito diferente, e impor um seria ser desobedecida por tres. */
.reel-dialog{
  /* Palco de tela cheia: e' dentro dele que o quadro cresce. */
  inline-size:100%;
  block-size:100%;
  /* hidden, e nao o overflow:visible da casca. O palco clipa: com um quadro
     maior que a janela -- a variacao de video em retrato, ou um
     --radius-media grande -- o que sobra fica dentro da tela em vez de
     pintar por fora dela. */
  overflow:hidden;
  /* Veu MAIS ESCURO que os 78% da casa, e isto e' desenho e nao descuido:
     video pede sala escura. Medido com uma dobra .sec-paper por tras, o
     composto vai de rgb(64,64,64) a 78% para rgb(40,40,40) a 88% -- 11
     pontos de L* em CIELAB, umas dez vezes o limiar do perceptivel, entao
     padronizar apagaria a intencao. Sobre dobra escura os dois graus dao o
     MESMO pixel: o grau do veu so' se ve quando ha claro atras.
     O ::backdrop herda custom property do elemento que o originou, e e' por
     isso que declarar --veil AQUI basta para a primitiva do core pintar o
     veu deste componente. */
  --veil:color-mix(in srgb, var(--dark) var(--reel-veil-mix, 88%), transparent);
}

.reel-dialog[open]{
  display:grid;
  place-items:center;
}

.reel-quadro{
  position:relative;
  inline-size:min(94vw, 1400px);
  aspect-ratio:16 / 9;
  max-block-size:88vh;
  border-radius:var(--radius-media);
  overflow:hidden;
  background:var(--dark);
  /* Os tres valores sao escritos pelo script: a diferenca entre onde a capa
     esta' e onde o quadro vai ficar. O transform sai para a identidade no
     quadro seguinte, e e' isso que faz o quadro CRESCER do lugar da capa
     em vez de aparecer no meio da tela. */
  transform:translate(var(--reel-dx, 0px), var(--reel-dy, 0px)) scale(var(--reel-s, 1));
  transform-origin:50% 50%;
  will-change:transform;
}

.reel-quadro.is-abrindo{
  transition:transform .58s var(--ease), filter .58s var(--ease);
  transform:none;
  filter:blur(0);
}

.reel-video{
  inline-size:100%;
  block-size:100%;
  object-fit:contain;
  display:block;
  background:var(--dark);
}

.reel-fechar{
  position:absolute;
  inset-block-start:14px;
  inset-inline-end:14px;
  display:grid;
  place-items:center;
  inline-size:44px;
  block-size:44px;
  border-radius:var(--radius-pill);
  border:1px solid color-mix(in srgb, var(--light) 34%, transparent);
  background:color-mix(in srgb, var(--dark) 52%, transparent);
  backdrop-filter:blur(8px);
  color:var(--light);
  cursor:pointer;
  /* Acima dos controles nativos do video, que o navegador desenha por cima
     do proprio elemento. */
  z-index:2;
}

.reel-fechar svg{
  inline-size:44%;
  stroke:currentColor;
  stroke-width:1.7;
}

/* ---- Variacao: capa compacta (.reel-capa--compacta) ----
   A capa foi desenhada para a largura da coluna: disco de 84px no meio e
   legenda de 1.3rem. Numa capa de 240 a 400px -- pousada sobre o letreiro
   do hero/letreiro-marca, num card de lateral -- isso mediu titulo, duracao
   e disco disputando o mesmo centro, com "Assistir ao video da casa"
   encostando no anel. Aqui o disco cai para 46px e a legenda para corpo de
   etiqueta.
   O seletor do disco tem duas classes de proposito: a media query de 760px
   la embaixo escreve --reel-play-size no proprio .reel-play, e uma classe
   so' perderia para ela no celular. */
.reel-capa--compacta .reel-play{--reel-play-size:46px}
.reel-capa--compacta .reel-legenda{padding:12px 14px;gap:10px}
.reel-capa--compacta .reel-titulo{font-size:13px;line-height:1.25}
.reel-capa--compacta .reel-dur{font-size:10.5px;white-space:nowrap}

@media (prefers-reduced-motion: reduce){
  /* O dialogo continua abrindo -- ele foi pedido. Some so' a coreografia:
     o quadro aparece no lugar final, sem crescer nem desfocar. */
  .reel-quadro{transform:none}
  .reel-quadro.is-abrindo{transition:none}
  .reel-poster,
  .reel-play{transition:none}
}

@media (max-width: 760px){
  .reel-play{--reel-play-size:64px}
  .reel-legenda{padding:18px 20px; gap:10px}
  .reel-quadro{inline-size:96vw}
}

@media (max-width: 460px){
  .reel-capa{aspect-ratio:4 / 3}
  .reel-legenda{flex-direction:column; align-items:flex-start}
}

script.js · 86 linhas · 3,6 KB

/* ==========================================================================
   SHOWREEL EXPANSIVEL — reel-
   Abre o <dialog> nativo e faz o quadro CRESCER do lugar exato da capa.

   O que NAO esta' aqui, porque o <dialog> ja' entrega: prender o foco
   dentro, fechar no Esc, devolver o foco ao botao que abriu, e tornar o
   resto da pagina inerte para tecnologia assistiva. E' o mesmo criterio do
   midia/comparador-antes-depois com o <input type="range">: usar o
   elemento nativo em vez de reimplementar o comportamento dele.
   ========================================================================== */
(function () {
  var abrir = document.querySelector('[data-reel-abrir]');
  var dlg = document.querySelector('[data-reel-dialog]');
  if (!abrir || !dlg) return;               // guard: saida silenciosa

  var quadro = dlg.querySelector('[data-reel-quadro]');
  var video = dlg.querySelector('[data-reel-video]');
  var fechar = dlg.querySelector('[data-reel-fechar]');
  if (!quadro) return;

  // showModal e' o que traz foco presto e ::backdrop. Sem ele o <dialog>
  // abre como caixa comum, e a pagina continua navegavel por tras.
  function abrirDialogo() {
    dlg.showModal();

    // Com movimento reduzido nao ha coreografia: o quadro ja' nasce no lugar
    // final (o CSS zera o transform) e nao ha nada a medir nem a animar.
    if (LIB.reduce) {
      tocar();
      return;
    }

    // FLIP: mede onde a capa esta' e onde o quadro ficou, e escreve a
    // DIFERENCA como transform. O quadro comeca desenhado sobre a capa.
    var a = abrir.getBoundingClientRect();
    var b = quadro.getBoundingClientRect();
    var escala = b.width ? a.width / b.width : 1;

    quadro.classList.remove('is-abrindo');
    quadro.style.setProperty('--reel-dx', ((a.left + a.width / 2) - (b.left + b.width / 2)).toFixed(1) + 'px');
    quadro.style.setProperty('--reel-dy', ((a.top + a.height / 2) - (b.top + b.height / 2)).toFixed(1) + 'px');
    quadro.style.setProperty('--reel-s', escala.toFixed(4));

    // Dois quadros de espera, nao um. Com um so', o navegador as vezes
    // agrupa a escrita do transform inicial com a remocao dele e a
    // transicao nao acontece -- o quadro simplesmente aparece no lugar.
    requestAnimationFrame(function () {
      requestAnimationFrame(function () {
        quadro.classList.add('is-abrindo');
        tocar();
      });
    });
  }

  function tocar() {
    if (!video || !video.play) return;
    // O play e' consequencia de um clique, entao o navegador costuma
    // permitir. "Costuma": politica de autoplay muda por navegador e por
    // configuracao, e um play recusado devolve promessa rejeitada. Sem o
    // catch, isso vira erro vermelho no console de quem so' abriu o video.
    var p = video.play();
    if (p && p.catch) p.catch(function () {});
  }

  function aoFechar() {
    if (video && video.pause) video.pause();
    quadro.classList.remove('is-abrindo');
    quadro.style.removeProperty('--reel-dx');
    quadro.style.removeProperty('--reel-dy');
    quadro.style.removeProperty('--reel-s');
  }

  abrir.addEventListener('click', abrirDialogo);
  if (fechar) fechar.addEventListener('click', function () { dlg.close(); });

  // 'close' dispara tanto pelo botao quanto pelo Esc: um lugar so' para
  // pausar o video, em vez de dois caminhos que podem divergir.
  dlg.addEventListener('close', aoFechar);

  // Clique no fundo fecha. O <dialog> nao faz isso sozinho, e e' a unica
  // coisa que falta nele. O alvo do clique ser o proprio dialogo significa
  // que caiu FORA do quadro -- dentro, o alvo seria o quadro ou o video.
  dlg.addEventListener('click', function (e) {
    if (e.target === dlg) dlg.close();
  });
})();

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

Quando não usar

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


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.