| 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:
mira-templates/decks/mira-studio-demo/index.html (projeto com Mira instalado)
templates/decks/mira-studio-demo/index.html (repositório fonte do Mira)
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:
camera → gere só <section data-layout="camera"><div class="cam-area"></div></section>. Nada de texto por cima (a fala é do apresentador).
split → título curto (máx. 6 palavras) + metáfora animada PENSADA PARA O QUADRADO (radial/orbital/hub rende mais) + .cam-area embaixo.
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 VideoFrames 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*