- name
- mira-vertical
- description
- Gera uma versão VERTICAL (9:16, generalista para a tela atual: largura da tela / 3 por altura cheia, ex. 640x1080 numa tela 1080p) de um deck do Mira a partir do 16:9, OU cria slides verticais DO ZERO quando não há deck de origem, para vídeo vertical (Reels, Shorts, TikTok, Stories). Não toca no original: gera index-9x16.html ao lado, com só o título no topo e a animação num canvas alto, e reformula o eixo de cada animação para o retrato. Use SEMPRE que o usuário disser /mira-vertical, versão vertical, deixa vertical, formato 9:16, 1080x1920, apresentação vertical, para Reels, para Shorts, para Stories, para TikTok, vídeo vertical, modo retrato, ou cria um slide vertical.
# Skill: Versão Vertical do Deck (9:16) com canvas alto, só título + animação
Transforma um deck 16:9 do Mira numa versão **vertical generalista para a tela atual**, para gravar como vídeo vertical (Reels, Shorts, TikTok, Stories). Cada slide de conteúdo vira **só o título no topo + a animação grande num canvas alto e padronizado**. Tem dois modos:
- **Modo conversão (padrão quando existe deck 16:9):** reformula um deck existente, slide a slide, conforme o playbook abaixo.
- **Modo criação nativa (quando NÃO existe deck de origem, ou o usuário pede um slide novo já vertical):** cria o slide do zero direto na geometria retrato, sem passar pelo 16:9. Veja a seção "Criação do zero na geometria nativa".
> **Fonte da verdade:** o padrão desta skill foi validado e aprovado no deck de referência `_reversa_sdd/slides-verticais/index-9x16.html`, e as regras estão congeladas na spec `_reversa_sdd/slides-verticais/mira-vertical-9x16-spec.md` (com o comparativo elemento por elemento em `mira-vertical-comparacao.md`). Quando em dúvida sobre um valor exato, o resultado deve ficar **idêntico ao deck de referência**.
## O resultado, em uma frase
Num slide de conteúdo da versão vertical aparecem **apenas duas coisas**: o **título principal colado no topo** (1 ou 2 linhas, nunca mais) e a **animação grande ocupando o canvas alto** logo abaixo, de ponta a ponta na largura da coluna. **Somem:** o subtítulo, o header do card (ícone + label + botão Replay) e a base do card (legenda uppercase + grade de pílulas). As laterais fora da coluna ficam **#000000**. Capa e encerramento (que não têm `.glass-card`) mantêm o layout próprio.
## Criação do zero na geometria nativa
Quando não houver deck 16:9 de origem, ou o usuário pedir "cria um slide vertical sobre X", NÃO crie um 16:9 intermediário para converter depois. A animação nasce pensada para o retrato:
1. **Herde as regras criativas do `agents/mira-animator/SKILL.md`:** metáfora primeiro, animação depois (método A/B antes de codar), Regra Zero (metáfora + história + loop interno obrigatório), refinamento sob demanda por slide, regra de idioma, regra de título (sem ícone, máximo 6 palavras), estrutura do card com glass-card. Tudo vale igual. Manter a estrutura do glass-card (header, `.anim-stage`, base de pílulas) é o que deixa o mesmo CSS desta skill esconder header/base e exibir só título + animação.
2. **Geometria nativa desde a concepção:** o arquivo já nasce como `index-9x16.html` (na pasta do deck novo), com o bloco `<style id="mira-formato-9x16">` desta skill no head, canvas alto padrão (`aspect-ratio: 128 / 203`) e `viewBox` retrato casando (`W = 960`, `H = W * 203 / 128 ≈ 1523`).
3. **Metáfora escolhida JÁ para o vertical:** o eixo dominante nasce na altura (fluxo desce, comparação empilha, rede espalha na vertical, escada sobe íngreme). Não componha mentalmente em 16:9 para depois girar; o playbook de reflow vira aqui um playbook de composição.
4. **Mesmos critérios de aprovação do modo conversão:** título no topo em no máximo 2 linhas, assunto ocupando bem a largura útil, nada cortado, REGRA DE FONTE MÍNIMA respeitada.
5. Se o deck vertical (`index-9x16.html`) já existir, o slide novo é adicionado nele, no padrão dos demais.
**Dimensão (o erro mais comum).** O quadro vertical é **generalista para a tela atual**, não um tamanho fixo em pixels. A **altura é a altura cheia da tela** (`100vh`) e a **largura é a largura da tela dividida por 3** (`calc(100vw / 3)`). Numa tela 1080p isso dá 640x1080; numa tela maior ou menor, escala junto. O quadro vertical é a **coluna central** (um terço da largura) ocupando toda a altura, com sobra dos dois lados como margem em #000000. Por que não 1080x1920 fixo: além de não caber numa tela de 1080 de altura, prenderia o resultado a uma única resolução. (A regra largura/3 dá uma coluna um tiquinho mais larga que o 9:16 cravado. Se a plataforma exigir 9:16 exato, use `--fmt-w: calc(100vh * 9 / 16)`.)
A abordagem **não é** moldura fixa que só encolhe: é **reformulação por slide**. Um `viewBox` 16:9 dentro de um quadro estreito e alto encaixa pela largura e ocupa só uma faixa fina, perdendo todo o impacto. Por isso aqui o palco vira **canvas alto** e a geometria de cada animação é **reformulada no JS da cópia** para subir e descer pela altura.
## Critério de aprovação (é para assistir no smartphone)
A animação domina a tela e o título é o único texto de apoio. Três coisas têm que estar certas:
1. **Composição enxuta: só título + animação.** Se aparecer subtítulo, header do card (ícone/label/Replay) ou a base de pílulas num slide de conteúdo, está ERRADO. Sobra do `.glass-card` apenas o filho do meio, o `.anim-stage`, sem fundo/borda/sombra/padding, de ponta a ponta.
2. **Título no topo, no máximo 2 linhas.** Colado no topo (não centralizado vertical), nunca em 3 ou 4 linhas. Títulos longos (palavras grandes como "documentação") encolhem sozinhos até caber em 2 linhas, via o script de auto-ajuste. Títulos curtos ficam no tamanho cheio.
3. **Animação vertical e maximizada no canvas alto.** Toda animação cujo eixo dominante era horizontal DEVE ser refeita na vertical (o que ia para o lado passa a ir de cima para baixo; lado a lado vira empilhado). Nada de partícula correndo numa faixa fina, nem blocos lado a lado encolhidos. O canvas é alto (128/203) e o `viewBox` é esticado em altura para casar, sem letterbox e sem distorção. Vale o padrão do mira-animator: espaço vazio é defeito de composição; na criação nativa, o que a escala não resolver vira cenário ambiente da própria metáfora (parado ou em deriva lenta, nunca focal).
## REGRA DE IDIOMA
Siga `agents/_shared/idioma.md`. Texto visível em português correto. Proibido travessão (—): use vírgula ou dois-pontos.
## REGRA DE FONTE MÍNIMA: 13px RENDERIZADOS
Validado empiricamente pelo usuário (teste de legibilidade em 2026-06-11): abaixo de 13px renderizados na tela, o texto fica ilegível no vertical. **Nenhum texto visível pode renderizar com menos de 13px.** Sem exceção: rótulos de nós, legendas de eixo, e qualquer texto SVG que permaneça visível.
- **Texto SVG (a pegadinha):** o `font-size` no SVG é em unidades do `viewBox`, não pixels de tela. O que renderiza um `font-size` SVG em pixels é a razão entre a **largura da coluna** e a **largura do `viewBox` (`W`)**, que **não muda** quando esticamos só a altura. Como a geometria do conteúdo (incluindo as fontes) é a mesma do reflow retrato e não é tocada (ver "Esticar o viewBox em altura"), o mínimo já validado continua valendo: no setup padrão (coluna de 1/3 da tela, `W ≈ 960`), use **`font-size >= 24`** para qualquer texto SVG (24 unidades renderizam ~12,9px). Se um slide tiver outro `W`, escale o mínimo na proporção da largura do `viewBox`.
- **Texto HTML** que sobrar visível (raro, já que header e base somem): `font-size` computado >= 13px. No Tailwind, `text-xs` (12px) está **PROIBIDO**; o menor permitido é `text-sm` (14px).
- **Se o texto não couber com 13px:** encurte o texto ou recomponha o layout. **Nunca** resolva diminuindo a fonte abaixo do mínimo.
## Regra de Ouro: nunca destrua o original
- O deck 16:9 (`index.html`) **permanece intacto**. Você nunca edita o arquivo de origem.
- Você cria um **arquivo novo** ao lado: `index-9x16.html`, e é nele que todo o reflow e a composição acontecem.
- O que você pode mudar na cópia: **geometria, layout e composição** (viewBox, coordenadas, eixo de espalhamento, altura do canvas, ocultar subtítulo/header/base via CSS, posição do título).
- O que continua **intocado** mesmo na cópia: textos, rótulos, cores, easing, durações, a lógica do loop interno e o `generation counter` (`window.__slugGen`) que evita vazamento. Você reposiciona e oculta, não reescreve a animação do zero.
## Como o Mira monta um slide (o que você vai reformular)
- Cada slide é um `body > section` com `class="min-h-screen flex flex-col items-center justify-center ..."`.
- O bloco do slide é o filho direto da `section`: `<div class="... max-w-6xl/max-w-5xl">`, com um `.text-center` (título `h2` + subtítulo `p`) e o `.glass-card` dentro.
- O `.glass-card` tem **três filhos diretos**: o **header** (1º `div`: ícone + label + sublinha + botão Replay), o **`.anim-stage`** (o palco da animação, o filho do meio) e a **base** (último `div`, o `.border-t`: legenda uppercase + grade de pílulas).
- O palco `.anim-stage` é `aspect-ratio: 16/9` no 16:9. Dentro dele há um `<svg id="sv-...">` que preenche 100% x 100%, com `preserveAspectRatio="xMidYMid meet"`.
- O `viewBox` da animação é definido **no JS** de cada slide (IIFE), normalmente como `const W = 960, H = 540; ... .attr('viewBox', \`0 0 ${W} ${H}\`)` (às vezes `1280 720`). As coordenadas derivam de `W` e `H`.
- Alguns slides já trazem um zoom do `mira-size-animator`: o `viewBox` vira `${(W - W/SZ)/2} ${(H - H/SZ)/2} ${W/SZ} ${H/SZ}` com uma constante `SZ`. Preserve essa fórmula (é o nível de tamanho do slide), só case a altura ao canvas alto.
- A navegação (barra de progresso, botão de próximo, teclado) é fixa e continua funcionando.
## As três viradas de chave
A reformulação gira em torno de três regras mecânicas.
### 1. Composição: só título + animação (CSS escopado)
Tudo via CSS no bloco injetado, escopado aos slides de conteúdo com `:has(.glass-card)` (capa e encerramento, sem `.glass-card`, ficam de fora):
- **Subtítulo oculto:** `body > section:has(.glass-card) .text-center > p { display: none }`.
- **Header e base do card ocultos:** `body > section .glass-card > div:first-child, body > section .glass-card > div:last-child { display: none }`.
- **Chrome do card zerado:** `.glass-card` sem `background`, `border`, `box-shadow`, `backdrop-filter` e `padding`, para a animação ir de ponta a ponta.
- **Conteúdo colado no topo:** `body > section:has(.glass-card) { justify-content: flex-start; padding: 2.2vh 6px 1.4vh 6px }`.
- **Título proeminente:** `body > section h2 { font-size: clamp(35px, 5.6vh, 52px); line-height: 1.1 }` (base ~7/10 da escala). O auto-ajuste por JS reduz só quando passa de 2 linhas.
Não remova os elementos do HTML: o CSS é cirúrgico e reversível, e o escopo `:has(.glass-card)` preserva capa, encerramento e qualquer slide-régua.
### 2. Canvas alto padrão (128/203) + esticar o viewBox em altura
- **Palco alto:** `.anim-stage { height: auto !important; aspect-ratio: 128 / 203 !important }` em TODOS os slides (≈0,6305, bem mais alto que o 4:5 antigo). `height: auto` é obrigatório, senão um `height` fixo por slide venceria o `aspect-ratio`.
- **viewBox casando, esticado SÓ em altura:** para cada `<svg id="sv-...">`, mantenha `minX`, `minY` e a largura `W`, e troque a altura por **`H = W * 203 / 128`**. A geometria desenhada (coordenadas dos elementos) **NÃO muda**: o conteúdo renderiza no mesmo tamanho e posição de antes, e o canvas só cresce para baixo. `preserveAspectRatio="xMidYMid meet"` continua, e como o aspecto do `viewBox` passa a bater com o do palco, não há letterbox nem distorção.
- Exemplo: `viewBox="140 175 1000 1250"` vira `viewBox="140 175 1000 1585.94"` (1000 * 203/128 = 1585,94).
- Exemplo real do deck de referência: `601.5 → 601.5 * 203/128 = 953.94`; `833.34 → 1321.63`.
### 3. Reflow do eixo para o retrato (geometria vertical)
Antes (ou junto com) o esticão de altura, a geometria de cada animação é reformulada para subir e descer pela altura, conforme o playbook por metáfora abaixo. Esse é o passo que garante que a animação não fica como faixa fina horizontal: o eixo dominante horizontal vira vertical, elipse larga vira alta, comparação lado a lado vira empilhada. O loop interno e o `generation counter` ficam intactos.
> O ajuste fino de espalhar o conteúdo para baixo (miolo vazio) e de ampliar uma animação são ferramentas por slide, não passos obrigatórios (ver "Ferramentas por slide").
## Auto-ajuste de título (script injetado)
Títulos longos quebram o slide em 3 ou 4 linhas. Um IIFE mede a altura real de cada `h2` e reduz a fonte 1px por vez (piso 18px) até caber em no máximo 2 linhas. Roda no `load`, no `document.fonts.ready` (reajusta quando a fonte Inter carrega) e no `resize` (debounced). Medir o render captura palavras longas reais (ex.: "documentação"), o que contar caracteres não pega. Injete junto aos outros scripts, antes de `lucide.createIcons()`:
```js
(function () {
const MAX_LINES = 2, MIN_FONT = 18;
function fitTitles() {
document.querySelectorAll('body > section h2').forEach(h2 => {
h2.style.removeProperty('font-size');
let font = parseFloat(getComputedStyle(h2).fontSize), guard = 0;
while (h2.scrollHeight > parseFloat(getComputedStyle(h2).lineHeight) * MAX_LINES + 2 && font > MIN_FONT && guard < 90) {
font -= 1; h2.style.setProperty('font-size', font + 'px', 'important'); guard++;
}
});
}
window.addEventListener('load', fitTitles);
if (document.fonts && document.fonts.ready) document.fonts.ready.then(fitTitles);
window.addEventListener('resize', () => { clearTimeout(window.__fitT); window.__fitT = setTimeout(fitTitles, 150); });
})();
```
## Playbook de reflow por metáfora
Aplique conforme a metáfora do slide. O princípio geral está no fim, para metáforas fora desta lista.
**Fluxo (partícula viajando entre nós).** Hoje é horizontal: `xs = scalePoint().range([120, W-120])`, partícula anima `cx`. Reflow para vertical:
- `const ys = d3.scalePoint().domain(d3.range(ETAPAS.length)).range([H * 0.10, H * 0.90]);` e `const cx = W / 2;` fixo.
- Linha vertical: `x1 = x2 = cx`, `y1 = ys(0)`, `y2 = ys(last)`.
- Nós: `translate(${cx}, ${ys(i)})`.
- Rótulo do nó: em vez de embaixo, jogue para o lado: `attr('dx', 70).attr('text-anchor', 'start')`, para o texto não colidir com o nó de baixo.
- Partícula: anima `cy` de `ys(0)` até `ys(last)`, com `cx` fixo.
- Pulsos e timings dos nós: iguais.
**Orbital (núcleo central + satélites).** Hoje é elipse larga: `rx = R, ry = R*0.62`. Reflow para elipse alta:
- Troque a compressão: `rx = R * 0.62, ry = R`.
- Satélites: `translate(${cx + Math.cos(d.angle) * R * 0.62}, ${cy + Math.sin(d.angle) * R})`.
- Aumente `R` para preencher a altura nova. Núcleo e pulso radial ficam no centro.
**Escada (orbe subindo degraus).** Reduza o avanço horizontal (`stepW` menor), aumente a subida (`stepH` maior) e recalcule `baseX`/`baseY` para a escada subir do rodapé até perto do topo do canvas alto. O orbe continua com o mesmo `climb()` e `easeBounceOut`.
**Comparação A vs B (dois cards lado a lado).** Layout HTML. Empilhe A em cima e B embaixo: troque o `grid md:grid-cols-2` por uma coluna só, mantendo o realce alternado (spotlight); se houver partícula viajando de A para B, faça-a descer.
**Transformação A → B lado a lado (planta → obra, antes → depois, entrada → saída).** Empilhe A em cima e B embaixo, cada um grande, e a seta/partícula de transformação **desce** de A para B. Nunca deixe os dois lado a lado encolhidos no topo. (É o caso típico de "miolo vazio": ver "Ferramentas por slide".)
**Rede / grafo / nuvem de nós (force layout).** Recentralize com `forceCenter(W/2, H/2)` no `H` novo; aumente o espalhamento (`forceManyBody().strength` mais negativo, maior `linkDistance`/`forceRadial`) até a nuvem usar a altura toda; cresça `forceCollide` com o raio dos nós; e clampe as posições em `[margem, H - margem]`:
```js
const MARGEM = 90;
sim.force('center', d3.forceCenter(W / 2, H / 2))
.force('charge', d3.forceManyBody().strength(-340))
.force('collide', d3.forceCollide(d => d.r + 8))
.on('tick', () => {
nodes.forEach(n => {
n.x = Math.max(MARGEM, Math.min(W - MARGEM, n.x));
n.y = Math.max(MARGEM, Math.min(H - MARGEM, n.y));
});
/* ...atualização de posições existente... */
});
```
Auf GitHub ansehen