- name
- mira-studio
- description
- Gera um deck VERTICAL 9:16 (1080x1920) pronto para gravar no OBS Studio, onde cada slide declara um de três layouts: camera (webcam ao vivo em tela cheia), split (animação quadrada estilo mira-squared em cima + câmera embaixo) e full (animação vertical estilo mira-vertical). A câmera do apresentador é embutida ao vivo no slide via getUserMedia (módulo mira/mira-camera.js), sem chroma key: no OBS basta capturar a janela. Sem câmera, a área vira verde chroma #00FF00 como plano B. Use SEMPRE que o usuário disser /mira-studio, deck para OBS, deck com câmera, slide com câmera, meio a meio, câmera embutida, deck para gravar vídeo comigo falando, ou pedir slides que misturam câmera e animação num vídeo vertical.
# Skill: Mira Studio (9:16 com câmera embutida, pronto para OBS)
## Ordem zero — não negociável
A primeira ação é resolver `deck_id = YYYY-MM-DD <slug>` com a data atual e criar, de uma vez, a pasta do deck e toda a árvore interna: `references/`, `assets/`, `assets/vendor/` e `mira/`. Nenhuma dessas pastas fica para depois. Isso acontece antes de mensagem intermediária, pergunta, leitura do deck de referência, coleta de roteiro, escolha de layout ou geração.
Cria decks verticais 9:16 para gravação de vídeo (Reels, Shorts, TikTok, videoaula) em que o apresentador aparece AO VIVO dentro do próprio slide. Cada slide declara um layout, e o usuário escolhe **slide a slide, na conversa**:
- **`camera`** — a webcam preenche a coluna inteira (você falando).
- **`split`** — quadrado 1:1 no topo com título + animação (padrão `mira-squared`) e a câmera preenchendo o resto embaixo (você + a metáfora).
- **`full`** — animação vertical em tela cheia (padrão `mira-vertical`, sem câmera).
> **Fonte da verdade:** o padrão desta skill está congelado no deck de referência (validado em 2026-07-11). Resolva o arquivo nesta ordem:
> 1. `mira-templates/decks/mira-studio-demo/index.html` (projeto com Mira instalado)
> 2. `templates/decks/mira-studio-demo/index.html` (repositório fonte do Mira)
> 3. `node_modules/mira-animator/templates/decks/mira-studio-demo/index.html`
>
> Se nenhum existir, peça para rodar `npx mira-animator update`. Em dúvida sobre um valor exato, o resultado deve bater com o deck de referência.
## O resultado, em uma frase
Uma coluna 9:16 central (laterais `#000000`) onde cada slide de CONTEÚDO (`body > section`) declara `data-layout="camera|split|full"` (capa e encerramento, sem `data-layout`, mantêm layout próprio): nas áreas `.cam-area` o módulo `mira/mira-camera.js` injeta o feed da webcam ao vivo (`object-fit: cover`, espelhado estilo selfie), nas áreas de animação valem as regras congeladas das skills irmãs, e o deck inteiro está pronto para o OBS capturar a janela sem chroma key.
## Fluxo conversacional (como o usuário monta o deck)
O usuário descreve o roteiro e diz o layout de cada slide, por exemplo: "slide 1 só câmera, slide 2 meio a meio sobre X, slide 3 só animação sobre Y". Para cada slide:
1. `camera` → gere só `<section data-layout="camera"><div class="cam-area"></div></section>`. Nada de texto por cima (a fala é do apresentador).
2. `split` → título curto (máx. 6 palavras) + metáfora animada PENSADA PARA O QUADRADO (radial/orbital/hub rende mais) + `.cam-area` embaixo.
3. `full` → título curto + metáfora animada PENSADA PARA O RETRATO (eixo dominante vertical: fluxo desce, comparação empilha).
Se o usuário não declarar o layout de um slide, pergunte. A capa é opcional (deck de gravação pode começar direto no primeiro slide do roteiro); quando houver capa, ela segue a diretiva do título com `text-wrap: balance`.
## Dimensão
O quadro é **9:16 cravado e generalista para a tela**: `--fmt-w: calc(100vh * 9 / 16)`, `--fmt-h: 100vh` (numa tela 1080p, ~607x1080; o OBS recorta a coluna e grava em 1080x1920). Não fixe pixels. Diferente do `mira-vertical` clássico (`100vw/3`), aqui a proporção exata importa porque a saída é vídeo 9:16.
## As regras herdadas (não reinvente)
- **Área de animação do `split`:** é um quadrado (`aspect-ratio: 1/1`, lado = largura da coluna) com área segura proporcional de `4.63%` (50/1080), título dentro no topo e animação preenchendo o resto com `casarPalco` + `fitToArea` (código canônico em `agents/mira-squared/SKILL.md`). Vale o CRITÉRIO Nº 1: a animação preenche a maior parte do quadrado.
- **Slide `full`:** título no topo (máx. 2 linhas, IIFE `fitTitles`), palco ocupando todo o resto, metáfora com eixo vertical, `casarPalco` + `fitToArea` (playbook de composição em `agents/mira-vertical/SKILL.md`).
- **Regra Zero:** toda animação tem loop interno infinito com generation counter (`window.__slugGen`).
- **Padrão criativo do `agents/mira-animator/SKILL.md`:** metáfora primeiro, animação depois (método A/B antes de codar), refinamento sob demanda por slide, e espaço vazio é defeito de composição: o que a ação não usa vira cenário ambiente da própria metáfora (parado ou em deriva lenta, nunca focal).
- **Idioma:** `agents/_shared/idioma.md`. Proibido travessão; acentuação correta.
- **Fonte mínima:** nenhum texto renderiza abaixo de 13px (SVG: `font-size >= 24` para `W = 960`).
- **Cor:** laranja da marca `#FF904D`; sem arco-íris.
- **Todo deck:** os 5 módulos em `mira/` referenciados antes de `</body>`, nesta ordem: `mira-edit.js`, `mira-edit-free.js`, `mira-draw.js`, `mira-camera.js`, `mira-record.js`. Libs vendoradas em `assets/vendor/`.
## O módulo mira/mira-camera.js (contrato)
Fonte canônica em `templates/authoring/mira-camera.js`; copie para `mira/` do deck. O que ele garante:
- **Stream único por sessão:** um só `getUserMedia({video, audio: false})` memoizado; todas as `.cam-area` compartilham o mesmo `MediaStream` (uma permissão, sem flicker). Cada `.cam-area` é um SINK `<video>` separado desse stream único — o stream nunca é duplicado, mas os elementos de vídeo sim.
- **Escalabilidade (muitos slides de câmera):** com mais de 2 `.cam-area`, o módulo anexa o stream só nas câmeras do slide visível (e vizinhos, via `IntersectionObserver`) e solta ao sair, mantendo ~O(1) sinks ativos mesmo em decks de 10/30 slides de câmera (evita 30 texturas de vídeo ociosas). O stream/permissão continua único; só o `srcObject` dos `<video>` é ligado/desligado. Deck com ≤2 câmeras anexa tudo (custo desprezível). A gravação é indiferente ao total de slides: captura só a coluna do slide visível e encoda no Worker.
- **Vídeo sempre mudo:** o áudio da gravação é do OBS/microfone.
- **Fallback verde chroma:** sem câmera (contexto `file://`, permissão negada, sem dispositivo), cada `.cam-area` ganha `.cam-fallback` (fundo `#00FF00` PURO, nada por cima) e um aviso discreto aparece FORA da área, sumindo em 5s. Plano B: filtro Chroma Key do OBS.
- **Tecla C:** alterna espelhamento (`body.cam-mirror`, padrão LIGADO, estilo selfie). Fica quieto durante os modos E/P e digitação.
- **Encerramento:** tracks paradas no `pagehide`.
A skill NÃO reimplementa nada disso: só marca as áreas com `.cam-area` e inclui o script.
## O módulo mira/mira-record.js (gravação nativa, sem OBS)
Fonte canônica em `templates/authoring/mira-record.js`; copie para `mira/` do deck. Painel de gravação no lado DIREITO da tela (fora da coluna, portanto fora do vídeo):
- **Grava SOMENTE a área dos slides:** captura a própria aba (`getDisplayMedia` com `preferCurrentTab`) e tenta recortar a track para a coluna 9:16 via **Region Capture** (`CropTarget.fromElement` + `track.cropTo`). O Worker nunca confia só na Promise: valida `displayWidth/displayHeight` de cada frame. Entrada 9:16 segue direta; full-tab é recortada pelas coordenadas normalizadas no `OffscreenCanvas`; se a proporção do frame já não corresponder ao viewport congelado, a fração incompatível é descartada e entra um crop central 9:16 seguro. Nunca estique o frame. Saída H.264 `avc1` com resolução constante por sessão: **1080x1920** em Alta ou a resolução 9:16 nativa em Desempenho.
- **Pipeline em Worker (desempenho — a razão de não travar):** todo o caminho captura→escala→encode→mux roda **fora do main thread**, num Worker dedicado. `MediaStreamTrackProcessor` puxa `VideoFrame`s direto da track recortada (sem `<video>`, sem `requestVideoFrameCallback`, sem canvas no main thread) e o `readable` é transferido ao Worker; lá dentro o `VideoEncoder` (fixo em 1080x1920, escala interna; fallback `OffscreenCanvas` no próprio Worker) codifica com backpressure (`encodeQueueSize>=2` descarta), keyframe a cada 2 s, e o mp4-muxer faz o mux (`firstTimestampBehavior: 'offset'`, por trilha — **nunca** `cross-track-offset`, que deslocaria o vídeo em horas). O Worker é criado por **Blob URL de dentro do próprio módulo** — não há arquivo `.js` novo para copiar no deck. O main thread só renderiza a página (câmera ao vivo + animações) e recebe o MP4 pronto no fim. **É isso que mantém câmera e slides fluidos durante a gravação, com CPU ou GPU** — o encoder de hardware só acelera a compressão, não a preparação do frame.
- **CFR (edição) — o vídeo entra no Premiere sem drift de áudio:** chave no painel, **ligada por padrão**. O Worker põe cada frame num slot da grade de 1/FPS antes do encoder: frame fora do slot é remarcado, dois frames no mesmo slot descartam o segundo, e slot vazio é preenchido com o quadro anterior (congela; teto de 2 s, acima disso salta e contabiliza). O áudio NÃO é tocado. **Por que isso existe:** sem a grade, a trilha de vídeo sai VFR (timestamps de captura + descarte por backpressure) — VLC e Chrome honram PTS e tocam certo, mas editor não trabalha em VFR: o Premiere conforma o clipe numa grade fixa e cada buraco da linha do tempo vira deslocamento ACUMULADO contra o áudio AAC, pior quanto mais longo o clipe. Desligar a chave volta ao VFR de antes (menos CPU, para quem só publica direto). Custo: cada buraco preenchido é um encode a mais — o painel mostra `N dup` e `N salto` ao vivo, e o JSON de diagnóstico traz `timing: {mode, dupFilled, dupDropped, gapJumped}`. **Isso é DIFERENTE do alinhamento inicial entre as trilhas** (BUG-20260815-HYRG, corrigido em 2026-08-16). Atenção ao que este texto dizia antes e estava ERRADO: que o offset inicial "já é resolvido pelo `firstTimestampBehavior: 'offset'`". Não era. No `mp4-muxer`, `'offset'` zera CADA trilha na própria origem e descarta a distância entre elas (medido: -30,4 ms numa gravação real). E trocar a constante para `'cross-track-offset'` sozinha é pior: o vídeo já chega em zero pela grade CFR, então `Math.min(0, ~290 s) = 0` e o áudio vai parar a minutos de distância, que é o commit 6e84363. O alinhamento agora é do gravador: `mandaAoMux()` leva as duas trilhas a uma origem comum antes do muxer, com guarda para relógios incomparáveis, e o desvio medido aparece no painel (`A/V ±N ms`) e no diagnóstico. Contrato completo no adendo `_reversa_sdd/addenda/bug-BUG-20260815-HYRG-v001.md`.
- **Três informações separadas, sem promessa de NVENC:** (1) `GPUs instaladas`, inventário Win32 vindo de `/__mira/gpus`; (2) `Renderer ativo`, detectado pelo WebGL e escolhido pelo Chrome/Windows; (3) `encoder` **Auto / Hardware preferido / Software (CPU)**, que mapeia para `hardwareAcceleration` (`no-preference`/`prefer-hardware`/`prefer-software`). GPUs instaladas NUNCA viram opções do encoder: uma página não escolhe a placa física nem confirma NVENC. O teste real de encode confirma apenas que a preferência foi aceita. Em `file://`, o painel explica que o inventário requer o launcher/localhost. Se o renderer continuar na integrada, oriente Configurações do Windows > Sistema > Tela > Gráficos para o `chrome.exe`; não automatize configuração do sistema. O mux MP4 usa `assets/vendor/mp4-muxer.js`; áudio do microfone usa AAC quando suportado.
- **Fallback de compatibilidade:** navegador sem WebCodecs/`MediaStreamTrackProcessor`/`OffscreenCanvas` cai no caminho antigo — `MediaRecorder` sobre um canvas fixo 1080x1920 alimentado por `requestVideoFrameCallback` (MP4/`avc1` 12 Mbps, ou WebM com aviso). Só roda quando não há o pipeline em Worker.
- **Métricas reais ao vivo:** durante a gravação o painel mostra `fps efetivo · % descartado · fila do encoder · Mbps real · MB` (reportado pelo Worker). É o diagnóstico honesto — se o % descartado sobe ou o fps cai de 20, é o sinal para trocar para o modo Desempenho ou checar `chrome://gpu`. Abaixo de ~20 fps por 3s o painel também avisa uma vez (trecho de tela estática não conta como lentidão). Em notebook, grave na tomada.
- **Diagnóstico de recorte e navegação:** o painel registra `input`, `crop`, `output`, caminho `direct/canvas`, maior long task, gap de rAF e gap PTS na janela de cada `mira-navigation`. Ao finalizar, o botão **salvar diagnóstico JSON** permite anexar as métricas à evidência. Os limites Windows são long task ≤50 ms e gap PTS ≤100 ms na troca.
- **Qualidade × Desempenho:** seletor no painel. **Alta** = 1080×1920 (padrão de reels). **Desempenho** = grava na resolução NATIVA da coluna (~608×1080 num display comum), ~3× menos pixels a codificar — a alavanca real para máquina fraca, independente de CPU/GPU. Resolução constante mantém o MP4 `avc1` válido; o bitrate é escalado proporcionalmente.
- **Gravação longa — direto no disco, sem teto (padrão):** a chave **`salvar direto no disco`** (ligada por padrão) faz o Worker escrever o MP4 **no arquivo enquanto grava** (File System Access + `FileSystemWritableFileStreamTarget` do mp4-muxer, com `fastStart: false`). Nada acumula em RAM: a gravação dura o que o disco aguentar, e o `finalize()` deixa de copiar centenas de MB. O usuário escolhe o arquivo **uma vez, antes de começar** (o `showSaveFilePicker` exige ativação do usuário, por isso vem antes do seletor de captura); ao parar, o arquivo já está lá — não há download. **Preço honesto:** sem o buffer inteiro em mãos, o índice do MP4 (`moov`) vai para o **fim** do arquivo. Parar normalmente escreve o índice e o vídeo abre em qualquer player; um travamento do navegador no meio deixa um MP4 com os dados e **sem índice** (ilegível sem reparo). Se o seletor for cancelado, a gravação nem começa (e o arquivo de 0 byte é removido).
- **Gravação em memória (fallback):** sem File System Access (`file://`, outros navegadores) a chave fica desligada e cinza, e o mux volta a ser in-memory — com o teto real do `ArrayBuffer` (~2 GB): avisa por volta de ~384 MB e PARA sozinha perto de ~512 MB, que a 12 Mbps são uns **6 minutos**. Nesse modo, para clipes longos, grave em partes.
- **Microfone opcional:** toggle no painel, mixado na gravação (a câmera já está composta no slide).
- **Controles:** botão Gravar/Parar no painel ou tecla **R** (quieta nos modos E/P e digitação). Ao iniciar, o usuário escolhe "Esta guia" no seletor do navegador. O arquivo baixa sozinho ao parar (`mira-reels-<timestamp>.mp4`, ou `-PARCIAL` quando houve perda no caminho de encode/leitura/flush).
- **Recorte da coluna:** tenta **Element Capture** (`restrictTo` na seção visível) quando o deck declara `window.__miraElemCapture`; a track já sai 9:16 e o teleprompter fica fora do vídeo. O `restrictTo` é **reaplicado a cada troca de slide** (evento `mira-navigation` e `scroll`), senão o vídeo congela no slide anterior. Sem a flag ou sem suporte, cai no **Region Capture** de sempre.
- O painel some em telas estreitas (a coluna ocupa tudo) e nunca aparece na gravação.
O OBS continua como alternativa (captura de janela + recorte); a gravação nativa é o caminho sem instalação.
## Teleprompter que não entra no vídeo (padrão de todo deck mira-studio)
Todo deck nasce com o teleprompter em **duas peças**, mais o editor de overlays e a persistência no arquivo. Os blocos canônicos estão no deck de referência; copie-os como estão.
- **Painel lateral (`#mira-prompter`, tecla T):** fica na margem cinza, FORA da coluna. É o **editor**: as quatro chaves, o texto do slide (`#mp-body`, `contenteditable`) e o botão "Salvar no arquivo". **Some durante a gravação** (`html[data-mira-recording] #mira-prompter { display: none }`) — quem se lê no ar é o overlay.
- **Overlay central (`#tp-ov`, tecla O):** o retângulo que o apresentador lê, por cima da coluna (fundo preto 60%, texto branco 60%). É **irmão das `<section>`, nunca filho** — é exatamente isso que permite excluí-lo do vídeo.
- **Texto por slide:** vem do **`roteiro.md` na raiz do deck** (seção abaixo), não do HTML. Editar no painel reflete no overlay na hora, grava no `.md` e persiste em `localStorage['mira-tp-text']` (cópia de trabalho). O texto troca ao navegar (`scroll` + `mira-navigation`).
- **Element Capture (tecla G):** a gravação nativa restringe a captura à **subárvore da seção visível** (`RestrictionTarget.fromElement` + `track.restrictTo`), então tudo que não é descendente dela — o overlay e o painel — **não é pintado no vídeo**, mesmo sobreposto. O deck liga o recurso declarando `window.__miraElemCapture = true`; sem essa flag o `mira-record.js` usa o Region Capture de sempre.
- **Editor genérico de overlays (`.me-ov`, tecla E):** qualquer camada sobre a câmera marcada com `class="me-ov" data-me-chrome data-me-key="<id>"` (mais um filho `<div class="me-ov-grip" data-me-chrome></div>`) vira movível e redimensionável no modo E. O `data-me-chrome` é obrigatório: sem ele o `mira-edit-free` disputa o mesmo clique e seleciona o `<img>` interno em vez do grupo.
- **Salvar no arquivo (botão ou Ctrl+S):** grava o estado editável de LAYOUT (posições e tamanhos dos overlays, **sem o texto do roteiro**) no bloco `<script id="mira-studio-state" type="application/json">` do próprio `index.html` — em localhost por `POST /__mira_save`, em `file://` pela File System Access API. No load, um IIFE **semeia o `localStorage` a partir desse bloco**: o **arquivo é a fonte da verdade**, não o navegador. No template o bloco começa `{}`.
**Onde cada overlay mora (a regra de ouro):** o que deve entrar no vídeo (logo, selo, animação sobre a câmera) fica **DENTRO** da `<section>`; o que não deve (teleprompter, painéis) fica **FORA** dela, como filho direto de `body`.
### Restrições honestas (diga isto ao usuário, não prometa mais)
- Excluir o teleprompter do vídeo só funciona no **gravador nativo (tecla R)**. **No OBS não**: ele grava os pixels da janela, e nada exclui um overlay. Para gravar no OBS, use só o painel lateral (que já fica fora da coluna recortada).
- Element Capture exige **Chrome/Edge desktop recente** e **contexto seguro** (localhost ou https). Sem suporte, o deck cai no Region Capture e o **overlay some durante a gravação** (fallback no CSS, via `html[data-mira-recording]:not([data-mira-elemcapture])`), para não vazar.
- `body > section { isolation: isolate }` é **pré-requisito**: sem stacking context o Chrome aceita o `restrictTo` e não emite frame — o MP4 sai vazio.
- Salvar por localhost exige o **servidor com os endpoints** (`/__mira_save`, `/__mira_meta`): num deck antigo, **reinicie o launcher** depois de atualizar o `mira-studio-server.cjs`.
## Roteiro externo `roteiro.md` (todo deck de gravação nasce com um)
O roteiro NÃO mora mais dentro do HTML. Todo deck gerado leva um **`roteiro.md` na raiz**, ao lado do `index.html`: é dele que saem os slides (layout e título) e a fala de cada slide. O apresentador escreve o roteiro no editor de texto que quiser, versiona em git, e vê o resultado no deck aberto sem recarregar.
**Gramática (uma linha por cabeçalho):**
```
<intro: texto livre, documenta a gramática para o usuário>
## Slide 1 | capa | Um roteiro, *três formatos*
View on GitHub