# Metaphi: YouTube Jukebox V3

> Toca-discos de música de fundo para sites e sistemas: um disco no canto da tela, um leque de capas em 3D e o som do YouTube num player invisível. Um arquivo, JavaScript puro, sem dependências. Versão 3.0.0.

- Página: https://metaphi.tech/componentes/youtube-jukebox
- Arquivo (minificado): https://metaphi.tech/jukebox/v3/metaphi-jukebox.min.js
- O arquivo publicado é minificado e ofuscado, com a licença MIT e os créditos no comentário do topo.
- Licença: Licença MIT (MIT) · Autor: Deivison Arthur (deivison@metaphi.tech)

## Em resumo

- **O que é:** Web Component `<metaphi-jukebox>` (Custom Element com Shadow DOM) que coloca música de fundo do YouTube em qualquer site ou sistema.
- **Como aparece:** um disco no canto da tela (o vinil gira enquanto toca) e, ao clicar, um painel com um leque de capas em 3D.
- **Instalação:** um arquivo JavaScript (48 KB com gzip), sem dependências, sem o script da API do YouTube e sem chave de API.
- **Navegadores:** o som começa dentro de um clique ou toque (regra dos navegadores); com `autoplay`, no primeiro clique em qualquer lugar da página.
- **Recursos para sistemas:** músicas enviadas pela pessoa (link do YouTube com mais de 25 minutos, por padrão) e continuar de onde parou depois de recarregar. Recomendados só em sistemas, não em sites institucionais.
- **Requisitos:** Chrome e Edge 111+, Safari 16.4+, Firefox 113+.

## Instalação

1. Baixe o arquivo para o seu projeto:

```bash
curl -fsSL https://metaphi.tech/jukebox/v3/metaphi-jukebox.min.js -o metaphi-jukebox.js
```

2. Carregue como módulo e adicione o elemento:

```html
<!-- 1. O componente: uma vez por página, perto do fim do <body> -->
<script type="module" src="/metaphi-jukebox.js"></script>

<!-- 2. O toca-discos, com as suas músicas -->
<metaphi-jukebox>
  <script type="application/json">
    { "tracks": [
      { "id": "Wv5kt7d8p1A", "title": "City Ghosts", "subtitle": "NEROX · Gangster Trap" },
      { "id": "7MHgdIu7FlI", "title": "Soft Collision", "subtitle": "Titan Beats · Deep House Mix" },
      { "id": "lxw2v6NzkW0", "title": "High Frequency", "subtitle": "DROVIX · Tech House Club Vibes" },
      { "id": "soiMEbHrKIY", "title": "Melodic Techno & House", "subtitle": "Insidious Space · Mix" },
      { "id": "e94CjYUK9Kw", "title": "Turn The Lights Off", "subtitle": "Kato feat. Jon · Nightwhisper Remix" },
      { "id": "1UdKb-aiiCw", "title": "Minimal Mix", "subtitle": "DROVIX · Tech House Club Vibes" },
      { "id": "qXWfvr6h6AE", "title": "High Frequency 05", "subtitle": "noDropz · Tech House Club Vibes" },
      { "id": "Tx9zMFodNtA", "title": "METAPHI Playlist", "subtitle": "Shuffle · 11 tracks", "playlist": ["Tx9zMFodNtA", "SAd_oy2Sc3Y", "NEMsIHVejWM", "6QBy_Oa5ZmM", "YrLk4vdY28Q", "StZcUAPRRac", "n1kI5puqLUI", "YBRCvVpknvg", "EImVucJO7Ok", "svitEEpI07E", "v0nmHymgM7Y"], "cover": {"lg": "https://metaphi.tech/music/metaphi-playlist.webp", "sm": "https://metaphi.tech/music/metaphi-playlist-sm.webp"} }
    ] }
  </script>
</metaphi-jukebox>
```

Ou por JavaScript:

```js
// metaphi-jukebox.js: o arquivo baixado pelo comando de instalação
import { createJukebox } from './metaphi-jukebox.js';

const jukebox = createJukebox({
  tracks: [
    { id: 'Wv5kt7d8p1A', title: 'City Ghosts', subtitle: 'NEROX · Gangster Trap' },
    { id: '7MHgdIu7FlI', title: 'Soft Collision', subtitle: 'Titan Beats · Deep House Mix' },
    { id: 'lxw2v6NzkW0', title: 'High Frequency', subtitle: 'DROVIX · Tech House Club Vibes' },
    { id: 'soiMEbHrKIY', title: 'Melodic Techno & House', subtitle: 'Insidious Space · Mix' },
    { id: 'e94CjYUK9Kw', title: 'Turn The Lights Off', subtitle: 'Kato feat. Jon · Nightwhisper Remix' },
    { id: '1UdKb-aiiCw', title: 'Minimal Mix', subtitle: 'DROVIX · Tech House Club Vibes' },
    { id: 'qXWfvr6h6AE', title: 'High Frequency 05', subtitle: 'noDropz · Tech House Club Vibes' },
    { id: 'Tx9zMFodNtA', title: 'METAPHI Playlist', subtitle: 'Shuffle · 11 tracks', playlist: ['Tx9zMFodNtA', 'SAd_oy2Sc3Y', 'NEMsIHVejWM', '6QBy_Oa5ZmM', 'YrLk4vdY28Q', 'StZcUAPRRac', 'n1kI5puqLUI', 'YBRCvVpknvg', 'EImVucJO7Ok', 'svitEEpI07E', 'v0nmHymgM7Y'], cover: { lg: 'https://metaphi.tech/music/metaphi-playlist.webp', sm: 'https://metaphi.tech/music/metaphi-playlist-sm.webp' } },
  ],
});

// eventos: jukebox:play, jukebox:pause, jukebox:trackchange…
jukebox.addEventListener('jukebox:trackchange', (e) => console.log(e.detail.track.title));
```

Em React (o mesmo vale para Next.js, Vue e Svelte: crie num efeito do navegador e remova ao desmontar):

```jsx
import { useEffect } from 'react';
// metaphi-jukebox.js: o arquivo baixado pelo comando de instalação
import { createJukebox } from './metaphi-jukebox.js';

const tracks = [
  { id: 'Wv5kt7d8p1A', title: 'City Ghosts', subtitle: 'NEROX · Gangster Trap' },
  { id: '7MHgdIu7FlI', title: 'Soft Collision', subtitle: 'Titan Beats · Deep House Mix' },
  { id: 'lxw2v6NzkW0', title: 'High Frequency', subtitle: 'DROVIX · Tech House Club Vibes' },
  { id: 'soiMEbHrKIY', title: 'Melodic Techno & House', subtitle: 'Insidious Space · Mix' },
  { id: 'e94CjYUK9Kw', title: 'Turn The Lights Off', subtitle: 'Kato feat. Jon · Nightwhisper Remix' },
  { id: '1UdKb-aiiCw', title: 'Minimal Mix', subtitle: 'DROVIX · Tech House Club Vibes' },
  { id: 'qXWfvr6h6AE', title: 'High Frequency 05', subtitle: 'noDropz · Tech House Club Vibes' },
  { id: 'Tx9zMFodNtA', title: 'METAPHI Playlist', subtitle: 'Shuffle · 11 tracks', playlist: ['Tx9zMFodNtA', 'SAd_oy2Sc3Y', 'NEMsIHVejWM', '6QBy_Oa5ZmM', 'YrLk4vdY28Q', 'StZcUAPRRac', 'n1kI5puqLUI', 'YBRCvVpknvg', 'EImVucJO7Ok', 'svitEEpI07E', 'v0nmHymgM7Y'], cover: { lg: 'https://metaphi.tech/music/metaphi-playlist.webp', sm: 'https://metaphi.tech/music/metaphi-playlist-sm.webp' } },
];

export function Jukebox() {
  useEffect(() => {
    const jukebox = createJukebox({
      tracks,
    });
    return () => jukebox.remove();
  }, []);

  return null;
}
```

## Músicas

| Campo | Tipo | Descrição |
| --- | --- | --- |
| `id` | texto | Id do vídeo (11 caracteres) ou o link inteiro (youtube.com/watch?v=…, youtu.be/…, /shorts/…, /live/…). |
| `title` | texto | Título no leque e no leitor de tela. |
| `subtitle` | texto | Linha de baixo: artista, gênero, duração. |
| `playlist` | texto[] | Disco-playlist: vários vídeos, tocados em sequência pelo próprio player (sem precisar de outro toque no celular), com capa empilhada e selo "Playlist · N". |
| `cover` | texto | { lg, sm } | Capa própria. Sem ela, a miniatura do YouTube (i.ytimg.com); na mídia própria, uma capa desenhada com o título. |
| `src` | texto | Mídia própria: o endereço de um arquivo de áudio (mp3, m4a, ogg…) no seu servidor, no lugar do vídeo do YouTube. Toca por `<audio>`, sem bloqueios do YouTube, e pode tocar em segundo plano (`background`). |

> **Aviso:** o YouTube ou o dono do vídeo podem bloquear a música a qualquer momento (vídeo removido ou privado, incorporação desativada, erros 101 e 150, bloqueio por direitos autorais ou por país, checagem anti-robô). O componente pula para o próximo disco (até 3 seguidos). O jeito mais seguro: músicas sem direitos autorais ("No Copyright") ou mídia própria.

### Músicas sem direitos autorais já usadas

Mixes da FreeWave Music marcados pelo canal como "No Copyright" (confira a licença na descrição do vídeo antes de usar):

- [Morning Chill House #12](https://youtu.be/_3cu8xOA-Tk) · FreeWave Music
- [Morning Chill House #4](https://youtu.be/fUHjIZNKTaY) · FreeWave Music
- [Morning Chill House #14](https://youtu.be/cCvrx1N5R2g) · FreeWave Music
- [Morning Chill House #8](https://youtu.be/wy9mFK5a6JA) · FreeWave Music
- [Morning Chill House #13](https://youtu.be/tbLHqmwFfm0) · FreeWave Music
- [Sunset Drive #9](https://youtu.be/-jYPxknmhmI) · FreeWave Music
- [Sunny Road Trip #5](https://youtu.be/gCHg8E9CFMg) · FreeWave Music

### Playlist com músicas curtas

Um disco com `playlist` toca vários vídeos curtos em sequência: a lista vai inteira para o player do YouTube, que passa de uma faixa à outra sozinho (no celular, sem outro toque) e recomeça no fim. A ordem é sorteada a cada vez (`shuffle`; use `shuffle="false"` para a ordem da lista). `show-playlists="false"` esconde os discos-playlist. Links `list=` do YouTube não servem: liste os vídeos.

```json
{ "title": "Playlist da casa", "subtitle": "Shuffle · 4 faixas", "cover": { "lg": "/musica/playlist.webp", "sm": "/musica/playlist-sm.webp" },
  "playlist": ["https://youtu.be/SAd_oy2Sc3Y", "NEMsIHVejWM", "6QBy_Oa5ZmM", "YrLk4vdY28Q"] }
```

### Mídia própria (arquivos de áudio)

Use `src` com o endereço de um arquivo de áudio (mp3, m4a, ogg) no lugar do vídeo. O disco toca por `<audio>`, sem bloqueios do YouTube, com controles na tela de bloqueio (Media Session). Com `background`, continua tocando com a página em segundo plano ou a tela bloqueada; o YouTube não permite isso no celular (o navegador pausa o player). O servidor precisa aceitar `Range` (206). No iPhone, o volume é o do aparelho.

```json
{ "tracks": [
  { "src": "/musica/abertura.mp3", "title": "Abertura", "subtitle": "Trilha da casa", "cover": "/musica/abertura.webp" },
  { "id": "_3cu8xOA-Tk", "title": "Morning Chill House #12", "subtitle": "FreeWave Music · No Copyright" }
] }
```

## Aparência

Cores, tamanho e lugar do disco na tela. Tudo muda ao vivo, sem recriar o componente.

| Atributo | Opção JS | Tipo | Padrão | Descrição |
| --- | --- | --- | --- | --- |
| `accent` | `accent` | cor CSS | `#2d81ff` | Cor de destaque: botão de tocar, régua, volume, progresso e o selo das músicas enviadas. |
| `surface` | `surface` | cor CSS | `#121316` | Fundo do painel e do botão de liga/desliga. |
| `ink` | `ink` | cor CSS | `#f2f1ee` | Cor do texto e das linhas (as transparências saem dela). |
| `radius` | `radius` | número (px) | `0` | Arredondamento dos cantos. 0 deixa os cantos retos, como no METAPHI. |
| `position` | `position` | texto | `bottom-left` | Canto da tela: bottom-left, bottom-right, top-left ou top-right. |
| `mobile-position` | `mobilePosition` | texto | `bottom-right` | Canto em telas estreitas (até `breakpoint`). No celular, o canto direito fica mais perto do polegar. |
| `cover-size` | `coverSize` | número (px) | `56` | Tamanho da capa no canto. Em telas estreitas ela usa 79% disso. |
| `offset-x` | `offsetX` | número (px) | `14` | Distância da borda lateral. |
| `offset-y` | `offsetY` | número (px) | `20` | Distância da borda de baixo (ou de cima). |
| `blur` | `blur` | número (px) | `10` | Desfoque do vidro do painel. |
| `backdrop` | `backdrop` | booleano | `true` | Escurece e desfoca a página atrás do painel aberto. |
| `breakpoint` | `breakpoint` | número (px) | `760` | Largura do componente abaixo da qual o painel vira uma folha na base da tela. |
| `z-index` | `zIndex` | número | `41` | Camada do componente na página. |
| `contained` | `contained` | booleano | `false` | Fica dentro do elemento `target` (que precisa de `position: relative`) em vez de fixo na tela (para demonstrações dentro de um bloco da página). |

## Disco e leque

O vinil no canto e o leque de capas em 3D do painel: giro, inclinação, profundidade e navegação.

| Atributo | Opção JS | Tipo | Padrão | Descrição |
| --- | --- | --- | --- | --- |
| `rpm` | `rpm` | número | `33` | Rotações por minuto do vinil enquanto toca (33, 45, 78…). Vale para o disco do canto e os do leque. |
| `peek` | `peek` | booleano | `true` | O vinil espia para fora da capa com o mouse em cima. |
| `visible` | `visible` | número | `3` | Capas visíveis de cada lado da capa em foco (1 a 5). |
| `fan` | `fan` | número (°) | `6` | Inclinação de cada capa vizinha. |
| `turn` | `turn` | número (°) | `22` | Quanto as vizinhas giram para longe. |
| `depth` | `depth` | número | `0.5` | Quanto as vizinhas recuam e diminuem. |
| `spread` | `spread` | número | `0.58` | Distância até as primeiras vizinhas, em fração da capa. |
| `stack` | `stack` | número | `0.5` | Quanto as capas distantes se empilham: 0 dá espaçamento igual, 1 fica no estilo coverflow. |
| `dim` | `dim` | número | `0.45` | Quanto as capas distantes apagam. |
| `auto-rotate` | `autoRotate` | booleano | `true` | O leque avança sozinho enquanto nada toca. |
| `interval` | `interval` | número (s) | `4` | Intervalo do avanço automático, em segundos. |
| `pause-on-hover` | `pauseOnHover` | booleano | `true` | Pausa o avanço com o cursor em cima do painel. |
| `loop` | `loop` | booleano | `true` | A lista dá a volta nas pontas. |
| `draggable` | `draggable` | booleano | `true` | Arrastar passa as capas, com inércia e encaixe. |
| `wheel` | `wheel` | booleano | `true` | A roda do mouse (ou o trackpad) passa as capas. |

## Playlist

Um disco que toca várias músicas curtas do YouTube em sequência, como a "METAPHI Playlist" (11 faixas). O próprio player passa de uma faixa à outra, sem precisar de outro toque no celular. Recomendado para site institucional.

| Atributo | Opção JS | Tipo | Padrão | Descrição |
| --- | --- | --- | --- | --- |
| `show-playlists` | `showPlaylists` | booleano | `true` | Mostra os discos-playlist no leque. Desligado, o leque fica só com os discos de um vídeo. Para montar a sua playlist com músicas comuns, veja "Playlist com músicas curtas". |
| `shuffle` | `shuffle` | booleano | `true` | As faixas da playlist tocam em ordem aleatória (sorteada de novo a cada vez que o disco começa). Desligado, tocam na ordem da lista. |

## Reprodução

Volume, ordem das músicas, o domínio do player do YouTube e quando ele carrega.

| Atributo | Opção JS | Tipo | Padrão | Descrição |
| --- | --- | --- | --- | --- |
| `volume` | `volume` | número (1–100) | `60` | Volume ao ligar a música. |
| `autoplay` | `autoplay` | booleano | `true` | Ligado por padrão: toca no primeiro clique, toque ou tecla em qualquer lugar da página, o gesto que os navegadores exigem para liberar som. O player carrega logo depois da página. Se a pessoa desligar a música, ela não volta sozinha. Para só tocar pelo disco do canto, use `autoplay="false"`. |
| `show-progress` | `showProgress` | booleano | `false` | Mostra a linha do tempo da música no painel (posição, duração e arrastar para avançar). Útil para mixes longos em sistemas; em site institucional, fica de fora. |
| `start` | `start` | texto | `random` | Disco inicial: `random` (sorteado a cada visita), `first`, a posição (`0`, `1`…) ou o id do vídeo. |
| `advance` | `advance` | texto | `next` | Ao terminar uma música: `next` (o próximo disco), `random` (outro, sorteado) ou `repeat` (a mesma de novo). |
| `background` | `background` | booleano | `false` | Mídia própria (discos com `src`) continua tocando com a página em segundo plano ou a tela bloqueada, com os controles do sistema. Músicas do YouTube não têm essa opção: no celular, o navegador pausa o player quando a página sai da tela. |
| `skip-errors` | `skipErrors` | booleano | `true` | Vídeo indisponível pula para o próximo disco (até 3 seguidos; depois, aparece o link "Ouvir no YouTube"). |
| `host` | `host` | texto | `auto` | `auto`: youtube.com no computador (vale o login do YouTube do navegador e evita a checagem anti-robô) e youtube-nocookie.com no celular. Ou force `youtube` ou `nocookie`. |
| `prepare` | `prepare` | texto | `auto` | Quando o player do YouTube carrega. `auto`: na primeira interação com a página (computador) ou logo depois do carregamento (celular, para o primeiro toque já tocar). Também: `interaction`, `load`, `hover` (ao chegar perto do disco) e `manual`. |
| `lang` | `lang` | texto | `auto` | Idioma dos textos: `pt-BR`, `en` ou `es`. `auto` segue o `lang` da página. |

## Recursos para sistemas

Músicas da própria pessoa e continuar de onde parou. Desligados por padrão: recomendados só em sistemas.

> **Recomendado só em sistemas.** Músicas próprias e continuar de onde parou fazem sentido onde a mesma pessoa volta sempre e está identificada: apps, plataformas, áreas logadas, painéis. Em site institucional, landing page ou portfólio, mantenha a seleção curada (o padrão).

| Atributo | Opção JS | Tipo | Padrão | Descrição |
| --- | --- | --- | --- | --- |
| `allow-custom` | `allowCustom` | booleano | `false` | Mostra "Adicionar música": a pessoa cola um link do YouTube, o componente confere a duração no próprio player e o vídeo vira um disco. |
| `show-examples` | `showExamples` | booleano | `true` | Mostra o botão "Exemplos" na janela de envio, com links prontos para testar (veja "Exemplos para testar"). Troque a lista com a opção `examples` ou desligue onde não fizer sentido. |
| `min-duration` | `minDuration` | número (min) | `25` | Duração mínima da música enviada, em minutos (0 aceita qualquer duração). O padrão pede mixes e sets com mais de 25 minutos. |
| `max-duration` | `maxDuration` | número (min) | `720` | Duração máxima aceita, em minutos (padrão: 12 horas). |
| `max-custom` | `maxCustom` | número | `8` | Quantas músicas enviadas ficam guardadas. Ao enviar mais uma, a mais antiga sai. |
| `storage` | `storage` | texto | `local` | `local`: as músicas enviadas ficam no navegador da pessoa (localStorage). `none`: o seu sistema guarda (no servidor, pela conta da pessoa) com `onTrackAdd`/`onTrackRemove` e devolve em `customTracks`. |
| `resume` | `resume` | booleano | `false` | Continuar de onde parou: guarda o disco, a posição e se a música estava ligada. Ao recarregar, o mesmo disco continua do mesmo ponto no primeiro toque, clique ou tecla. |
| `save-interval` | `saveInterval` | número (s) | `4` | A cada quantos segundos a posição é guardada enquanto toca (também ao sair da página). |
| `storage-key` | `storageKey` | texto | `metaphi-jukebox` | Prefixo das chaves no localStorage: `<chave>:tracks` e `<chave>:point`. Use um por sistema (ou por usuário). |

### Exemplos para testar

O botão "Exemplos" da janela de envio mostra estes vídeos (troque com a opção `examples`; tire com `show-examples="false"`):

- [Sia · Unstoppable (Lyrics) · 30 Min](https://youtu.be/78kO0d0FxYo) · Gospel Lyrics · 29:58
- [Músicas internacionais românticas · anos 70, 80 e 90](https://youtu.be/kDd2FhSeptY) · Música internacional romântica imortal · 1h15
- [Grupo Menos é Mais · Churrasquinho Menos é Mais 3 (DVD completo)](https://youtu.be/qPgwDlqdiD0) · Grupo Menos é Mais · 42 min

O primeiro tem 29:58, por isso a duração mínima padrão é mais de 25 minutos.

## Opções só de JavaScript

| Opção | Tipo | Descrição |
| --- | --- | --- |
| `tracks` | `Track[]` | As músicas, na ordem do leque (veja "Músicas"). No HTML, vão num `<script type="application/json">` dentro do elemento. |
| `examples` | `(link \| { id, title, subtitle })[]` | Exemplos do botão "Exemplos" da janela de envio. Sem ela, os três exemplos padrão. |
| `customTracks` | `Track[]` | Músicas enviadas que o seu servidor devolve (com `storage: "none"`). Entram no fim do leque. |
| `labels` | `objeto` | Troca qualquer texto, ex.: `{ title: "Rádio", add: "Enviar mix" }`. |
| `target` | `Element \| seletor` | Onde criar o elemento (só em `createJukebox`). Padrão: `document.body`. |
| `onReady` | `(jukebox) => void` | O player do YouTube ficou pronto (ou desistiu: sem rede, bloqueado). |
| `onStatus` | `(status, jukebox) => void` | `playing`, `paused`, `waiting` (o navegador pediu um toque) ou `error`. |
| `onTrackChange` | `(detail, jukebox) => void` | Trocou de disco. `detail`: `{ index, track, playing, status, volume }`. |
| `onTrackAdd` | `(track) => Promise<Track \| void>` | Antes de guardar uma música enviada: salve no servidor e devolva os dados (ou lance um erro com a mensagem para a pessoa). |
| `onTrackRemove` | `(id \| null) => Promise<void>` | Antes de remover uma música enviada (`null`: todas). |

## API

| Método / propriedade | Descrição |
| --- | --- |
| `play(index?)` | Toca o disco `index` (posição ou id) ou o atual. Chame dentro de um clique/toque. |
| `pause()` | Desliga a música (conta como "a pessoa desligou"). |
| `toggle()` | Liga ou desliga. |
| `next() / previous()` | Próximo / disco anterior, já tocando. |
| `setVolume(0–100)` | Volume. 0 desliga. |
| `seek(segundos)` | Vai para um ponto da música atual. |
| `hold() / release()` | Pausa enquanto outro vídeo ou áudio da página toca e volta depois, sem contar como "a pessoa desligou". |
| `open() / close()` | Abre ou fecha o painel de discos. |
| `prepare()` | Carrega o player do YouTube agora, sem tocar. |
| `addTrack(link)` | Confere e adiciona uma música. Promise `{ track, removed, duplicate }`; erro com a mensagem pronta para mostrar. |
| `removeTrack(id)` | Remove uma música enviada. Promise `true`/`false`. |
| `clearCustomTracks()` | Remove todas as músicas enviadas. Promise com a quantidade. |
| `clearResume()` | Esquece o ponto guardado (continuar de onde parou). |
| `update(opções)` | Troca opções ao vivo. Aparência, leque e reprodução mudam sem recriar nada. |
| `destroy()` | Desmonta tudo (também acontece ao remover o elemento da página). |
| `state` | Leitura: `{ index, track, playing, status, volume, time, duration, open, held, ready }`. |
| `tracks · options · version` | Leitura: as músicas na ordem do leque, as opções atuais e a versão. |

## Eventos

Disparados no elemento (`bubbles` e `composed`).

| Evento | Descrição |
| --- | --- |
| `jukebox:ready` | O player do YouTube ficou pronto. |
| `jukebox:status` | `detail.status`: `playing`, `paused`, `waiting` ou `error`. |
| `jukebox:play / jukebox:pause` | O som começou ou parou de verdade (confirmado pelo player). |
| `jukebox:trackchange` | Trocou de disco. `detail`: `{ index, track, playing, status, volume }`. |
| `jukebox:open / jukebox:close` | O painel abriu ou fechou. |
| `jukebox:trackadd` | Entrou uma música enviada. `detail`: `{ track, removed }`. |
| `jukebox:trackremove` | Saiu uma música enviada. `detail.id` (`null`: todas). |
| `jukebox:update` | Opções trocadas. `detail.changed`: a lista. |

## Estilo

- Fontes: `--jukebox-font`, `--jukebox-font-mono`, `--jukebox-font-display` e `--jukebox-display-stretch` no elemento (herdam para dentro do Shadow DOM).
- Partes: `metaphi-jukebox::part(corner)`, `::part(panel)` e `::part(jukebox)`.
- Estado no elemento, para o CSS do site: `data-playing`, `data-open`, `data-ready` e `data-status`.
- Cliques dentro de elementos com `data-jukebox-keep-open` não fecham o painel.

## Navegadores e YouTube

- Nada do YouTube carrega antes da hora: no computador, na primeira interação com a página; no celular, logo depois do carregamento, para o primeiro toque já tocar.
- O "tocar" acontece dentro do clique ou toque. Se o navegador pedir outro gesto, o botão de som pisca (`waiting`) e o próximo toque toca.
- Ao voltar para a aba (o celular pausa o vídeo em segundo plano), a música continua.
- Requisitos: Chrome e Edge 111+, Safari 16.4+, Firefox 113+ (Custom Elements, Shadow DOM, `color-mix()` e unidades de container).

| Erro do YouTube | Significado |
| --- | --- |
| 2 | O link não é de um vídeo. |
| 5 | O vídeo não toca no navegador. |
| 100 | Vídeo não encontrado (removido ou privado). |
| 101 · 150 | O dono do vídeo não deixa ele tocar fora do YouTube. |

## Perguntas frequentes

### A música pode parar de tocar sozinha?

Pode, nos discos do YouTube: o vídeo pode sair do ar, ficar privado, ter a incorporação desativada pelo dono, ser bloqueado por direitos autorais ou por país. O componente pula para o próximo disco (até 3 seguidos). O mais seguro é usar músicas sem direitos autorais ("No Copyright") ou mídia própria (`src`).

### A música continua com a tela bloqueada ou em outra aba?

Com mídia própria e `background`, sim, com os controles do sistema na tela de bloqueio. Com o YouTube, não no celular: o navegador pausa o player quando a página sai da tela, e a música volta quando a pessoa retorna.

### Por que a música não começa sozinha ao abrir a página?

Os navegadores não deixam uma página tocar som sem um gesto da pessoa. O componente toca dentro do próprio clique ou toque. Por padrão (`autoplay`), a música começa no primeiro clique, toque ou tecla em qualquer lugar da página, como nesta página (`autoplay="false"` desliga); com "continuar de onde parou", ela volta do mesmo ponto nesse primeiro gesto.

### Funciona no iPhone?

Sim. No celular o player carrega logo depois da página, para o primeiro toque já tocar. Se o Safari ainda pedir outro gesto, o botão de som pisca (estado `waiting`) e o próximo toque toca. As playlists passam de uma faixa à outra dentro do player, sem precisar de outro toque.

### Apareceu "Ouvir no YouTube". O que houve?

O YouTube não tocou: vídeo indisponível, incorporação desativada pelo dono (erros 101 e 150) ou a checagem anti-robô do YouTube. Com `skipErrors`, o componente tenta o próximo disco antes de mostrar o link.

### Preciso de uma chave da API do YouTube?

Não. A duração, o título e o canal das músicas enviadas vêm do próprio player incorporado, e o controle é feito por postMessage, sem o script da API.

### Posso colar um link de playlist do YouTube (list=…)?

Não pelo campo de envio. Para tocar várias faixas em sequência, crie um disco com `playlist: ["id1", "id2", …]`.

### Funciona com Content Security Policy?

Sim. Libere `frame-src https://www.youtube.com https://www.youtube-nocookie.com` e `img-src https://i.ytimg.com` (e, se quiser o canal das músicas enviadas, `connect-src https://www.youtube.com`). Nenhum script do YouTube é carregado, e os estilos entram por adoptedStyleSheets e CSSOM, sem precisar de `unsafe-inline`.

### O que fica guardado no aparelho da pessoa?

Por padrão, nada. Com `allowCustom` e `storage: "local"`, as músicas enviadas ficam no localStorage (`<chave>:tracks`). Com `resume`, o disco, a posição e se a música estava ligada (`<chave>:point`). O player do YouTube segue as regras de cookies do YouTube quando carrega. No celular, por padrão, ele usa o domínio youtube-nocookie.com.

### Como troco as músicas da playlist?

Crie um disco com `playlist`: a lista dos vídeos (ids ou links do YouTube), mais `title`, `subtitle` e, de preferência, uma `cover` própria. Ele toca as músicas em sequência, sorteando a ordem (`shuffle`). Links de playlist do YouTube (list=…) não servem: liste os vídeos um a um.

### Achei um bug ou tenho uma ideia. Onde aviso?

Escreva para jukebox@metaphi.tech com o navegador, o aparelho e, se der, o link da página onde aconteceu.

### Como uso em React, Next.js ou Vue?

Crie com `createJukebox()` num efeito que roda só no navegador (useEffect, onMounted) e chame `.remove()` ao desmontar. No Next.js, use o componente só no cliente. A aba "Código" do painel Ao vivo já gera o exemplo em React.

## Licença e créditos

Uso livre: você pode usar, copiar, modificar e distribuir, inclusive em projetos comerciais, desde que mantenha o aviso de copyright e o texto da licença (o comentário no topo do arquivo, com os créditos). O componente é fornecido como está, sem garantia. O arquivo publicado é minificado e ofuscado; o código-fonte não é distribuído.

- Licença: [Licença MIT](https://opensource.org/license/mit) (`MIT`)
- Autor: Deivison Arthur · deivison@metaphi.tech
- LinkedIn: https://www.linkedin.com/in/deivisonarthur/
- Site: https://metaphi.tech
- Bugs e melhorias: jukebox@metaphi.tech

---

© 2026 Deivison Arthur (METAPHI Deep Tech) · https://metaphi.tech/componentes/youtube-jukebox
