Contrato de criação de apresentação de slides (Slideshow)
Um slideshow HyperFrames é uma composição HyperFrames normal — cenas, clipes, timelines GSAP — com um ingrediente extra: uma ilha JSON que declara quais cenas são slides e como eles se conectam. O SlideshowController do player lê a ilha e transforma a timeline GSAP contínua em um deck discreto e navegável.
Leia /hyperframes-core primeiro para obter o contrato base de composição (clipes, faixas, atributos data-*, regras de determinismo). Esta skill cobre apenas o que é novo: o esquema da ilha, regras de escrita de slides, fragmentos, ramificações, validação e o componente de invólucro.
Saída — um deck navegável, não um MP4 linear
A saída de um slideshow é o deck em execução: sirva-o com hyperframes present <project-dir> (ou modo "apresentar" do Studio) — o SlideshowController do player lê a ilha e gerencia navegação, fragmentos, ramificações e modo apresentador. Consulte Apresentação e entrega abaixo.
Não use hyperframes render para um slideshow gerar um único MP4. Um deck é criado como várias composições de cena de nível superior (um data-composition-id por slide) sem uma composição raiz-mestre que os envolva, portanto o render resolve apenas a primeira composição e emite um MP4 silenciosamente truncado (por exemplo, 6 segundos de um deck de 40 segundos). Uma exportação linear da linha principal (apenas slides principais, sequências de ramos excluídas) é adiada — até que seja implementada, as saídas suportadas são o deck present ao vivo e imagens estáticas por slide (snapshot). Se um usuário precisar de um MP4 linear hoje, mostre essa limitação em vez de apontar o render para o deck.
Confirmação de intenção
Se o usuário pedir explicitamente por uma apresentação de slides, slideshow ou slideshow HyperFrames, prossiga com esta skill.
Se a skill foi acionada por um pedido adjacente, como "apresentação", "pitch deck", "deck", "deck interativo" ou "converta esta página", pause antes de criar e apresente a escolha antes de pedir confirmação. Explique brevemente que um slideshow HyperFrames significa um deck executável com slides discretos, navegação integrada e modo apresentador, notas do orador editáveis, tratamento compartilhado de mídia e validação antes da entrega. Para conversões de página de origem, mencione também que o objetivo é preservar o design visual, as interações, o movimento e o comportamento de mídia da página original, traduzindo o movimento da página em transições entre slides.
Em seguida, faça uma pergunta curta de confirmação:
Você quer isso como um slideshow HyperFrames?
Use uma interface de escolha sim/não quando o ambiente fornecer uma; caso contrário, faça a pergunta em texto simples.
Não implemente o slideshow até que o usuário diga sim. Se ele disser não, pare de usar esta skill e mude para o fluxo de trabalho não-slideshow apropriado.
As duas partes
1. Cenas — declaradas da maneira normal
Cada slide é apoiado por uma cena. Declare cenas com data-composition-id, data-start, data-duration e data-label:
<div
data-composition-id="problem"
data-start="0"
data-duration="8"
data-label="The problem"
data-width="1920"
data-height="1080"
>
<!-- clips vão aqui -->
</div>
Slides de ramificação (acessíveis apenas por um hotspot, excluídos da linha principal) são declarados exatamente da mesma forma — eles só aparecem em uma entrada slideSequences na ilha, não no array slides principal.
2. A ilha JSON — um bloco de script por composição
Adicione exatamente um bloco <script type="application/hyperframes-slideshow+json"> ao HTML da composição. Ele contém todos os metadados do slideshow:
<script type="application/hyperframes-slideshow+json">
{
"slides": [...],
"slideSequences": [...]
}
</script>
A ilha é a fonte única da verdade para a ordem dos slides, notas, pontos de espera de fragmentos, hotspots e sequências de ramos. Mantenha-a perto do topo do <body>, antes das divs das cenas, para que seja fácil de encontrar.
Não esconda o manifesto do slideshow atrás de um bloco <script type="application/json"> alternativo mais código de tempo de execução que cria a ilha. O comando present lê o HTML da composição estaticamente e espera que a ilha application/hyperframes-slideshow+json real já esteja presente.
Esquema
SlideshowManifest (o objeto ilha de nível superior)
{
"slides": [
/* SlideRef[] — a linha principal, em ordem */
],
"slideSequences": [
/* SlideSequence[] — sequências de ramos fora da linha */
]
}
SlideRef
{
"sceneId": "problem",
"notes": "Comece pela dor, não pela empresa.",
"fragments": [3.5, 5.2, 7.0],
"hotspots": [
/* SlideHotspot[] */
],
"ttsScript": null,
"ttsAudioUrl": null,
"ttsDurationMs": null
}
| Campo | Obrigatório | Notas |
|---|---|---|
sceneId | sim | Deve corresponder exatamente ao data-composition-id de uma cena (ou fornecer startTime/endTime explícitos). A regra de lint resolve cenas por data-composition-id. |
notes | não | Texto apenas para o apresentador. Nunca mostrado ao público. |
fragments | não | Array de tempos (segundos) dentro do intervalo [início, fim] do slide — veja Fragmentos abaixo. |
hotspots | não | Sobreposições interativas que acionam uma ramificação — veja Ramificação abaixo. |
startTime | não | Opcional. Substitui os limites de tempo da cena correspondente; o padrão é o início/fim da cena. |
endTime | não | Opcional. Substitui os limites de tempo da cena correspondente; o padrão é o início/fim da cena. |
ttsScript, ttsAudioUrl, ttsDurationMs | não | Reservado. Os campos do esquema existem, mas a reprodução de TTS ainda não está conectada. Omita a menos que esteja pré-povoando para uma compilação futura. |
SlideHotspot
{
"id": "h1",
"label": "Como calculamos isso?",
"target": "market-deep-dive",
"region": { "x": 60, "y": 10, "w": 35, "h": 20 }
}
| Campo | Obrigatório | Notas |
|---|---|---|
id | sim | Único dentro do slide. |
label | sim | Texto da dica de ferramenta / botão mostrado ao público. |
target | sim | Deve corresponder a um SlideSequence.id em slideSequences. |
region | não | Caixa delimitadora percentual do slide: {x, y, w, h} em 0–100. Omita para renderizar o hotspot como um botão rotulado em tela cheia. |
SlideSequence
{
"id": "market-deep-dive",
"label": "Metodologia de dimensionamento de mercado",
"slides": [{ "sceneId": "mkt-1" }, { "sceneId": "mkt-2" }]
}
slides dentro de uma sequência usa a mesma forma SlideRef que a linha principal. Fragmentos e hotspots aninhados são permitidos.
Regras de escrita de slides
Estas são restrições rígidas, não sugestões. Um slide que as viole será substituído imediatamente quando um revisor o vir.
- O título é uma afirmação com frase completa, não um rótulo. Escreva "PMEs gastam 14 horas/semana em agendamento manual" não "Problema de agendamento". A frase deve ser autossuficiente se o visual for ignorado.
- Uma ideia + um visual por slide. Se você estiver tentado a adicionar um segundo bloco de marcadores ou um segundo gráfico, divida o slide.
- Comece com o argumento principal. O ponto mais forte vem primeiro — no slide e na ordem do deck. Investidores leem da esquerda para a direita, de cima para baixo, e param.
- Dimensionamento de mercado bottom-up apenas. Nunca escreva "TAM de $50B" sem mostrar a matemática. Construa a partir da economia unitária: contas × ACV, ou transações × taxa de aproveitamento.
- Tamanho mínimo de fonte equivalente a 30pt. Em 1920×1080, um título tem 72–96px; o corpo do texto tem 48px. Nunca vá abaixo de 40px para qualquer texto que o público precise ler.
Portabilidade de páginas de origem
Ao converter uma página existente em um slideshow, a fidelidade à origem faz parte do contrato. Não substitua widgets específicos da origem por aproximações simplificadas, a menos que o usuário peça explicitamente um redesign.
- Preserve o design visual, a linguagem de movimento, o comportamento interativo, o comportamento de mídia e os recursos de apresentação da página original o mais próximo possível. Quando o sistema de slideshow suportar modo apresentador, inclua notas do orador usando o comportamento compartilhado de notas editáveis, em vez de uma implementação específica do deck.
- Porte visuais mecânicos do DOM/CSS/JS de origem o mais exatamente possível: players personalizados, visualizadores de canvas, timelines, cabeças de reprodução, hastes, círculos expansíveis, estados de hover e outros detalhes interativos devem sobreviver à conversão.
- Trate elementos nativos
<video>/<audio>como a fonte da verdade para qualquer cromo de mídia personalizado, visualizador de canvas, forma de onda, grade de batida ou cabeça de reprodução. Conecte os eventos de mídia de origem (play,pause,timeupdate,seeking,seeked,ended,ratechange,volumechange) e derive o estado visual demedia.currentTime; não execute um temporizador separado que possa se desviar da reprodução real. - Cada
<video>ou<audio>copiado comsrcdeve ter atributos de tempo HyperFrames antes da validação:data-startedata-duration, maisdata-has-audio="true"quando o áudio nativo audível deve ser preservado. Use o intervalo de tempo da cena para mídia específica do slide; para vídeos de evidência controlados pelo usuário que podem ser reproduzidos de vários slides focados, use um intervalo de todo o deck. Não deixepreload="none"na mídia; usemetadataouauto. - Resolva os tokens de fonte de origem antes da validação. Se preservar fontes personalizadas de origem, adicione regras
@font-facepara arquivos de fonte locais/capturados. Se usar fallbacks de sistema, substitua declarações tokenizadas comofont-family: var(--f-body)por pilhas concretas seguras para renderização, comosystem-ui, sans-serifouui-monospace, monospace; não deixevar(...)como o valor da família de fontes. - Audite a origem em busca de movimentos de página atípicos, especialmente comportamento impulsionado por scroll, roda, toque, estado de hash, redimensionamento ou um loop requestAnimationFrame. Trate viewports fixas com camadas de "mundo" traduzidas/escaladas, paralaxe, painéis fixos, rolagens horizontais, timelines com scrub por scroll, encaixe de seção e câmeras de zoom em elemento como comportamento de origem. O scroll é frequentemente o gatilho de transição da origem, portanto, preserve a transição extraindo suas paradas de progresso, easing e estados de câmera/foco e, em seguida, reabrigue esse movimento na navegação do slideshow por meio de posições de timeline, fragmentos ou um gancho de player/harness reutilizável. Invólucros independentes que saltam para pontos de espera do slide ainda precisam de um gancho de transição de câmera de navegação explícito; calcular transformações de câmera por slide não é suficiente. Não simule uma transição literal de rolagem de página para baixo dentro do slide; o espectador deve sentir movimento/travel da câmera de um ponto focal para outro, não ver uma página da web sendo rolada. Mantenha cada movimento de câmera de slide para slide contínuo: evite paradas de rota intermediárias que invertam a direção x/y ou o zoom, a menos que a origem visivelmente faça isso no mesmo limite. Uma transição que se move erraticamente antes de pousar é pior do que um movimento focal direto mais simples.
- Preserve a semântica de corte de mídia da origem. Trate capturas de tela, tweets/postagens sociais, capturas de UI de produto, gráficos, documentos, código, placares de líderes e qualquer imagem com texto legível como evidência de conteúdo, não mídia decorativa: use a proporção de aspecto de origem (
height: auto) ouobject-fit: containdentro de um quadro estável. Useobject-fit: coverapenas quando a origem o usou, ou para miniaturas intencionalmente decorativas/de fundo/cinematográficas. Depois de encaixar essas capturas em um slide, inspecione todas as quatro bordas em busca de texto truncado, logotipos, controles ou legendas; um corte visível em conteúdo significativo é um bug, a menos que a própria origem o tenha cortado. - Se um comportamento é genérico para slideshows, coloque-o no player/controlador ou em um snippet de skill reutilizável. Não resolva com scripts avulsos do deck.
- Os quadros de cena empilhados nunca devem bloquear a interação no slide ativo. Os quadros ocultos precisam de ocultação visual e bloqueio de eventos:
.scene-frame {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.scene-frame.is-active {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
Se a visibilidade for impulsionada imperativamente, defina todas as três propriedades (opacity, visibility e pointerEvents) no controlador de visibilidade. opacity: 0 sozinho ainda deixa uma camada invisível que pode engolir cliques.
Fragmentos: pontos de espera de revelação dentro de um slide
Um fragmento é um tempo absoluto da timeline da composição (segundos) dentro do intervalo [início, fim] de um slide onde o controlador deve manter um estado de revelação.
Como funciona:
- O player entra em um slide fragmentado — busca diretamente para
fragments[0]e mantém lá. - O usuário pressiona Próximo (ou →) — o controlador busca para
fragments[1]e mantém. - Após o último fragmento, Próximo avança para o próximo slide.
- Um slide sem fragmentos entra em um quadro de descanso dentro do slide, geralmente seu ponto médio, não exatamente no
slide.end.
Os tempos dos fragmentos devem cair dentro de [start, end]
…
[truncado para tradução]