primocode 10.16.0 → 10.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/LICENSES/edit-ia-pro.txt +32 -0
  2. package/LICENSES/fontes-OFL.txt +476 -0
  3. package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
  4. package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
  5. package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
  6. package/LICENSES/three-MIT.txt +23 -0
  7. package/LICENSES/video-use-MIT.txt +24 -0
  8. package/LICENSES/woven-sfx-CC0.txt +16 -0
  9. package/LICENSES/yunet-MIT.txt +21 -0
  10. package/README.md +2 -2
  11. package/bin/primocode.js +11 -2
  12. package/editor/LICENSE.md +11 -0
  13. package/editor/README.md +321 -0
  14. package/editor/SKILL.md +307 -0
  15. package/editor/THIRD_PARTY_NOTICES.md +228 -0
  16. package/editor/assets/baixar.json +591 -0
  17. package/editor/assets/fonts/anton/OFL.txt +93 -0
  18. package/editor/assets/fonts/caveat/OFL.txt +93 -0
  19. package/editor/assets/fonts/figtree/OFL.txt +93 -0
  20. package/editor/assets/fonts/fonts.json +10 -0
  21. package/editor/assets/fonts/montserrat/OFL.txt +93 -0
  22. package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
  23. package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
  24. package/editor/assets/models/YUNET_LICENSE.txt +21 -0
  25. package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
  26. package/editor/assets/sfx/licencas/cash.txt +76 -0
  27. package/editor/assets/sfx/licencas/charge.txt +31 -0
  28. package/editor/assets/sfx/licencas/click.txt +46 -0
  29. package/editor/assets/sfx/licencas/coin.txt +50 -0
  30. package/editor/assets/sfx/licencas/count.txt +47 -0
  31. package/editor/assets/sfx/licencas/data.txt +31 -0
  32. package/editor/assets/sfx/licencas/error.txt +32 -0
  33. package/editor/assets/sfx/licencas/flash.txt +31 -0
  34. package/editor/assets/sfx/licencas/gears.txt +82 -0
  35. package/editor/assets/sfx/licencas/keyboard.txt +48 -0
  36. package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
  37. package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
  38. package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
  39. package/editor/assets/sfx/licencas/notification.txt +54 -0
  40. package/editor/assets/sfx/licencas/pop.txt +38 -0
  41. package/editor/assets/sfx/licencas/rewind.txt +32 -0
  42. package/editor/assets/sfx/licencas/rise.txt +46 -0
  43. package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
  44. package/editor/assets/sfx/licencas/snap.txt +50 -0
  45. package/editor/assets/sfx/licencas/success.txt +40 -0
  46. package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
  47. package/editor/assets/sfx/licencas/tick.txt +14 -0
  48. package/editor/assets/sfx/licencas/whoosh.txt +47 -0
  49. package/editor/assets/sfx/licencas/wind.txt +31 -0
  50. package/editor/assets/sfx/sfx.json +832 -0
  51. package/editor/assets/sfx/sfx_sinteticos.json +317 -0
  52. package/editor/assets/sfx/sfx_woven.json +387 -0
  53. package/editor/assets/sfx/woven/LICENCA.md +16 -0
  54. package/editor/fichas/briefing-do-video.md +45 -0
  55. package/editor/fichas/conferencia-da-amostra.md +56 -0
  56. package/editor/fichas/matriz-de-ganchos.csv +3 -0
  57. package/editor/fichas/padrao.json +16 -0
  58. package/editor/fichas/planilha-de-cenas.csv +4 -0
  59. package/editor/fichas/planilha-de-versoes.csv +4 -0
  60. package/editor/fichas/plano-em-blocos.md +32 -0
  61. package/editor/fichas/registro-de-materiais.json +27 -0
  62. package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
  63. package/editor/helpers/bordas_precisas.py +272 -0
  64. package/editor/helpers/camera_viva.py +426 -0
  65. package/editor/helpers/captions.py +601 -0
  66. package/editor/helpers/common.py +308 -0
  67. package/editor/helpers/compose_final.py +3555 -0
  68. package/editor/helpers/conferir_corte.py +234 -0
  69. package/editor/helpers/conferir_dados.py +405 -0
  70. package/editor/helpers/contact_sheet.py +282 -0
  71. package/editor/helpers/corte_por_onda.py +433 -0
  72. package/editor/helpers/corte_seco.py +595 -0
  73. package/editor/helpers/denoise.py +971 -0
  74. package/editor/helpers/efeitos.py +408 -0
  75. package/editor/helpers/efeitos_gerar.py +159 -0
  76. package/editor/helpers/elevenlabs_api.py +442 -0
  77. package/editor/helpers/face_layout.py +570 -0
  78. package/editor/helpers/fetch_images.py +423 -0
  79. package/editor/helpers/ganho_por_trecho.py +214 -0
  80. package/editor/helpers/gen_image.py +312 -0
  81. package/editor/helpers/gen_music.py +316 -0
  82. package/editor/helpers/gen_sfx.py +148 -0
  83. package/editor/helpers/grade.py +307 -0
  84. package/editor/helpers/icloud.py +119 -0
  85. package/editor/helpers/make_sfx.py +940 -0
  86. package/editor/helpers/matte.py +1342 -0
  87. package/editor/helpers/medir_referencia.py +2232 -0
  88. package/editor/helpers/mixar_trilha.py +354 -0
  89. package/editor/helpers/montar_corte.py +295 -0
  90. package/editor/helpers/motion/__init__.py +13 -0
  91. package/editor/helpers/motion/engine.py +1915 -0
  92. package/editor/helpers/motion/registry.py +510 -0
  93. package/editor/helpers/motion/render.py +665 -0
  94. package/editor/helpers/motion/templates/barra_progresso.py +370 -0
  95. package/editor/helpers/motion/templates/carimbo.py +132 -0
  96. package/editor/helpers/motion/templates/checklist.py +271 -0
  97. package/editor/helpers/motion/templates/comentario_cta.py +380 -0
  98. package/editor/helpers/motion/templates/contador.py +222 -0
  99. package/editor/helpers/motion/templates/destaque.py +263 -0
  100. package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
  101. package/editor/helpers/motion/templates/grafico_barras.py +510 -0
  102. package/editor/helpers/motion/templates/grafico_linha.py +462 -0
  103. package/editor/helpers/motion/templates/notificacao.py +266 -0
  104. package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
  105. package/editor/helpers/motion/templates/passos.py +321 -0
  106. package/editor/helpers/motion/templates/riscado.py +298 -0
  107. package/editor/helpers/motion/templates/selo_garantia.py +150 -0
  108. package/editor/helpers/motion_render.py +241 -0
  109. package/editor/helpers/motion_sugerir.py +961 -0
  110. package/editor/helpers/noise_check.py +134 -0
  111. package/editor/helpers/pack_transcripts.py +223 -0
  112. package/editor/helpers/palavras_na_onda.py +176 -0
  113. package/editor/helpers/plano_sfx.py +839 -0
  114. package/editor/helpers/portao.py +427 -0
  115. package/editor/helpers/preview_server.py +1177 -0
  116. package/editor/helpers/rastrear_rosto.py +298 -0
  117. package/editor/helpers/render_cut.py +1256 -0
  118. package/editor/helpers/respiros.py +89 -0
  119. package/editor/helpers/sincronizar_cameras.py +571 -0
  120. package/editor/helpers/speech_regions.py +423 -0
  121. package/editor/helpers/style_catalog.json +231 -0
  122. package/editor/helpers/style_samples.py +246 -0
  123. package/editor/helpers/tela_verde.py +2113 -0
  124. package/editor/helpers/text_render.py +2059 -0
  125. package/editor/helpers/texto_na_tela.py +366 -0
  126. package/editor/helpers/timeline_view.py +292 -0
  127. package/editor/helpers/tira_de_quadros.py +113 -0
  128. package/editor/helpers/transcribe.py +734 -0
  129. package/editor/helpers/transcribe_batch.py +129 -0
  130. package/editor/helpers/trilhas.py +1146 -0
  131. package/editor/helpers/trilhas_humores.json +66 -0
  132. package/editor/helpers/valores.py +187 -0
  133. package/editor/helpers/verify_cut.py +820 -0
  134. package/editor/helpers/voice_levels.py +233 -0
  135. package/editor/helpers/voz_check.py +386 -0
  136. package/editor/helpers/voz_limpa.py +81 -0
  137. package/editor/helpers/watch_preview.py +372 -0
  138. package/editor/modelo-projeto/LEIA.md +402 -0
  139. package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
  140. package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
  141. package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
  142. package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
  143. package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
  144. package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
  145. package/editor/modelo-projeto/assets/three/LICENSE +21 -0
  146. package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
  147. package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
  148. package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
  149. package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
  150. package/editor/modelo-projeto/build.py +4828 -0
  151. package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
  152. package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
  153. package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
  154. package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
  155. package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
  156. package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
  157. package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
  158. package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
  159. package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
  160. package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
  161. package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
  162. package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
  163. package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
  164. package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
  165. package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
  166. package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
  167. package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
  168. package/editor/modelo-projeto/hyperframes.json +12 -0
  169. package/editor/modelo-projeto/package.json +11 -0
  170. package/editor/modelo-projeto/projeto.json +16 -0
  171. package/editor/preview/app.css +1009 -0
  172. package/editor/preview/app.js +2122 -0
  173. package/editor/preview/index.html +307 -0
  174. package/editor/pyproject.toml +23 -0
  175. package/editor/references/3d.md +236 -0
  176. package/editor/references/animacao-catalogo.md +672 -0
  177. package/editor/references/animacao.md +498 -0
  178. package/editor/references/comandos.md +92 -0
  179. package/editor/references/corte-seco-como-funciona.md +50 -0
  180. package/editor/references/corte-seco.md +88 -0
  181. package/editor/references/corte.md +290 -0
  182. package/editor/references/direcao-por-blocos.md +218 -0
  183. package/editor/references/enquadramento.md +654 -0
  184. package/editor/references/estilos-prontos.md +355 -0
  185. package/editor/references/estudio.md +100 -0
  186. package/editor/references/exemplo-eventos.json +67 -0
  187. package/editor/references/lettering.md +252 -0
  188. package/editor/references/modelos.md +494 -0
  189. package/editor/references/phase2.md +1007 -0
  190. package/editor/references/referencia-aplicar.md +203 -0
  191. package/editor/references/referencia-medir.md +375 -0
  192. package/editor/references/revisao.md +335 -0
  193. package/editor/references/som-e-trilha.md +146 -0
  194. package/editor/references/tabela-de-sons.md +130 -0
  195. package/editor/references/variacoes.md +234 -0
  196. package/editor/uv.lock +1141 -0
  197. package/lib/act.js +36 -8
  198. package/lib/catalogo.js +4 -0
  199. package/lib/chamada-torta.js +81 -0
  200. package/lib/editor-pro.js +786 -0
  201. package/lib/entrega.js +58 -0
  202. package/lib/estudio/conferencia-visual.js +76 -0
  203. package/lib/estudio/diretor.js +6 -1
  204. package/lib/estudio/exportar.js +3 -1
  205. package/lib/estudio/gosto.js +45 -4
  206. package/lib/estudio/index.js +8 -5
  207. package/lib/estudio/kit-ae/base.jsx +119 -13
  208. package/lib/estudio/kit-ae/cenas.jsx +47 -15
  209. package/lib/estudio/kit-ae/guarda.jsx +274 -0
  210. package/lib/estudio/kit-ae/primitivas.jsx +180 -24
  211. package/lib/estudio/maquina-diretor.js +61 -7
  212. package/lib/estudio/maquina-foto.js +177 -0
  213. package/lib/estudio/maquina-guarda.js +207 -0
  214. package/lib/estudio/maquina-layout.js +22 -1
  215. package/lib/estudio/maquina.js +60 -11
  216. package/lib/estudio/referencias.js +1 -1
  217. package/lib/estudio/visao/visao.swift +97 -0
  218. package/lib/estudio/visao.js +60 -0
  219. package/lib/local/cerebro.json +192 -6
  220. package/lib/local/motor.js +5 -2
  221. package/lib/nuvem.js +11 -1
  222. package/lib/passagem.js +50 -0
  223. package/lib/perigo.js +19 -11
  224. package/lib/protecao.js +134 -0
  225. package/lib/titulo-conversa.js +46 -0
  226. package/lib/tools.js +3 -0
  227. package/lib/ui.js +1 -1
  228. package/package.json +3 -2
  229. package/skills/editar-meu-video/SKILL.md +6 -2
  230. package/skills/editar-video/SKILL.md +113 -0
  231. package/studio/pessoa.py +52 -0
  232. package/voz/primo_voz/app.py +43 -4
  233. package/voz/primo_voz/chat.html +17 -6
  234. package/voz/primo_voz/painel_dados.py +134 -0
@@ -0,0 +1,672 @@
1
+ # Catálogo de animações
2
+
3
+ Cada tipo tem: quando usar, os campos no `projeto.json`, o que o `build.py` escreve (HTML, CSS e GSAP) e o som
4
+ que pede. Os trechos saíram de uma composição real gerada pelo modelo (estilo `papelaria`, 1080x1920); os
5
+ números mudam com o rosto e com a fala de cada vídeo. Se nenhuma peça servir, escreva uma cena sob medida à
6
+ mão (`cenas_proprias` no `projeto.json`, contrato em `modelo-projeto/compositions/cenas/LEIA.md`) seguindo os
7
+ mesmos padrões e as pegadinhas do fim.
8
+
9
+ ## O esqueleto que todas usam
10
+
11
+ O `index.html` tem o vídeo, a câmera, a legenda e um lugar (host) para cada peça. Cada peça do catálogo vira
12
+ uma sub-composição própria em `compositions/<id>.html`, e por isso ganha uma faixa com nome na linha do
13
+ tempo do estúdio ("Rótulo · Produto digital", "Texto atrás · O FIM"). As cenas sob medida entram do mesmo
14
+ jeito, de `compositions/cenas/`.
15
+
16
+ ```html
17
+ <script src="assets/gsap.min.js"></script> <!-- ou o CDN do GSAP 3.14.2 -->
18
+ <div id="root" data-composition-id="main" data-start="0" data-duration="10.0" data-width="1080" data-height="1920" data-fps="30">
19
+ <div id="palco"> <!-- tudo que treme junto no carimbo -->
20
+ <div id="bg"></div> <!-- papel quadriculado (moldura e tela verde) -->
21
+ <div id="borda"></div> <!-- aro da moldura -->
22
+ <div id="frame"> <!-- enquadramento: clip-path no frame, x/y/scale na .camera -->
23
+ <div id="inner" class="camera">
24
+ <video id="cam" src="assets/cut.mp4" muted playsinline data-start="0" data-duration="10.0" data-media-start="17.9"
25
+ data-track-index="0" data-timeline-label="Vídeo do corte"></video>
26
+ </div>
27
+ <div id="atras"> ...hosts do texto atrás da pessoa... </div>
28
+ <div id="innerp" class="camera"> ...pessoa recortada (webm com transparência)... </div>
29
+ </div>
30
+ <div id="pecas">
31
+ <div id="faixa-tag" class="faixa" data-composition-id="peca-tag" data-composition-src="compositions/tag.html"
32
+ data-start="2.39" data-duration="1.62" data-track-index="10" data-timeline-label="Rótulo · Produto digital"
33
+ data-width="1080" data-height="1920"></div>
34
+ <div class="cap caption-cue clip" id="cap0" data-start="0" data-duration="1.34" data-track-index="1"
35
+ data-track-kind="captions" data-timeline-label="Legenda · EU GRAVEI">EU GRAVEI</div>
36
+ </div>
37
+ </div>
38
+ <audio id="voz" src="assets/cut.mp4" data-start="0" data-duration="10.0" data-media-start="17.9" data-track-index="80"></audio>
39
+ </div>
40
+ <script>
41
+ const tl = gsap.timeline({ paused: true }); // câmera, moldura, legenda e a tremida do carimbo
42
+ window.__timelines["main"] = tl;
43
+ </script>
44
+ ```
45
+
46
+ E cada peça, em `compositions/tag.html` (o build gera e apaga a cada vez; não edite à mão):
47
+
48
+ ```html
49
+ <template id="peca-tag-template">
50
+ <style>
51
+ @font-face{font-family:Montserrat;src:url("assets/fontes/Montserrat.ttf") format("truetype");font-weight:100 900;font-display:block}
52
+ #root{position:absolute;inset:0;font-family:Montserrat}
53
+ .rotulo{...}
54
+ </style>
55
+ <div id="root" data-composition-id="peca-tag" data-width="1080" data-height="1920" data-duration="1.72">
56
+ <div class="rotulo" id="tag" style="left:264px;top:444px;width:551px;height:100px;font-size:46px"><span>Produto <b>digital</b></span></div>
57
+ </div>
58
+ <script>
59
+ (function () {
60
+ const tl = gsap.timeline({ paused: true });
61
+ tl.fromTo("#tag",{opacity:0,y:-44},{opacity:1,y:0,duration:0.35,ease:"back.out(2)",immediateRender:false},0);
62
+ tl.to("#tag",{opacity:0,y:-28,duration:0.25,ease:"power3.in",immediateRender:false},1.161);
63
+ window.__timelines["peca-tag"] = tl;
64
+ })();
65
+ </script>
66
+ </template>
67
+ ```
68
+
69
+ - Os trechos de JS das seções abaixo mostram o tempo como se tudo estivesse numa timeline só (tempo do
70
+ corte menos o início da amostra). No arquivo gerado, cada peça conta o tempo a partir do começo dela
71
+ (0 = primeiro movimento da peça); o host no `index.html` põe esse 0 no lugar certo (`data-start`). Se a
72
+ amostra começa no meio da peça, o host leva `data-playback-start` e a peça continua do ponto certo.
73
+ - Na linha do tempo do estúdio: vídeo, legenda (faixa 1, um clipe por legenda), peças (10 em diante), cenas
74
+ sob medida (30 em diante), pessoa recortada (40), estrutura (câmera, moldura, papel: 60 em diante), voz (80)
75
+ e sons (81 em diante). Faixa com mais de um clipe aparece como "Track N".
76
+ - Tempo na timeline = tempo do corte menos o início da amostra (no `completo`, o próprio tempo do corte).
77
+ Nunca negativo.
78
+ - Toda peça nasce com `opacity: 0` no CSS e entra por `fromTo`. Depois do primeiro tween de um elemento,
79
+ `immediateRender: false`.
80
+ - Posição fixa no `style` (left/top) é calculada uma vez pelo `build.py`; o movimento é só `x`, `y`,
81
+ `rotation`, `opacity` e `clip-path`, sem escala (pegadinha 25): dois renders do mesmo projeto saem iguais.
82
+ - Imagem de peça (polaroide, objeto na mão, frente, logo do cartão final) vai no tamanho em que aparece: o
83
+ build reduz uma cópia para `assets/gerado/<nome>_<largura>x<altura>` (a original não muda; imagem que já cabe
84
+ fica como está).
85
+ - Visual `papelaria`: cartão `#fffdfd` com base lilás (`box-shadow: 0 12px 0 -2px #c7b7ef, 0 22px 44px
86
+ rgba(53,44,64,.35), inset 0 -8px 0 rgba(199,183,239,.35)`), texto `#352c40`, destaque `#665080`,
87
+ verde `#2e9e5b`, vermelho `#e5484d`, Montserrat 900 e Caveat 700 (fontes em `assets/fontes/`).
88
+ - Visual `laranja` (`"estilo": "laranja"`): cartão escuro `#15161d` com fio laranja (`box-shadow: 0 0 0 2px
89
+ rgba(255,122,26,.6), ...`), texto claro `#f5f5f7`, destaque `#ff7a1a` (o texto e o ícone sobre o laranja saem
90
+ `#140a02`), verde `#35d07f`, vermelho `#ff3b30`, fundo `#0c0d13` com grade e brilho laranja. A mesma peça, as
91
+ cores vêm dos tokens do estilo (`ESTILOS` no `build.py`).
92
+
93
+ Campos comuns a todos os tipos: `id` (sem espaço, único), `tipo`, `em` (segundos ou a palavra),
94
+ `depois_de`, `sai` (segundos ou a palavra), `sai_depois_de`, `antecipa` (padrão 0,1 s), `onde` (`auto`,
95
+ `acima`, `topo`, `baixo_esquerda` ou `[x, y]`), `esconde_legenda` (`true` quando a peça já escreve a fala).
96
+ Peça com o `sai` no fim do corte (até 0,25 s antes) fica até o último quadro, sem saída (com `segura_fim`, até o fim
97
+ do quadro parado).
98
+
99
+ **Na moldura, o quadro vence.** A moldura leva 0,7 s para fechar (a pessoa encolhe da tela cheia para o
100
+ quadro). Peça cuja palavra cai nesse tempo (ou até 0,4 s antes da troca) entra quando o quadro termina de
101
+ fechar, e o build avisa (`aviso: lista: entra em 7.79 s, quando o quadro da moldura terminou de fechar`). Os
102
+ itens de lista, passo, notificação e foto que ficariam antes disso entram logo depois, em ordem. A peça chega
103
+ uns 0,3 a 0,6 s depois da palavra: é melhor que cobrir o rosto ainda grande. Numa cena própria, faça igual com
104
+ `window.__cenas[...].moldura.fechada_em` (o build avisa quando a cena começa antes).
105
+
106
+ **Legenda não é peça.** A legenda do modelo tem dois estilos (`"legenda": {"estilo": "padrao"}` ou
107
+ `"discreta"`), no máximo 3 palavras por vez (`max_palavras`), e nenhuma palavra dita fica de fora (a frase se divide
108
+ por sentido; a palavra que piscaria junta com a vizinha). Legenda "papelaria" (a frase se escrevendo à
109
+ mão), "Impacto" ou "Manchete" é cena própria: `"esconde_legenda": true` na cena e a fala escrita por ela, com as
110
+ `palavras` do trecho (`modelo-projeto/compositions/cenas/LEIA.md`).
111
+
112
+ ---
113
+
114
+ ## 1. Rótulo em cima da cabeça (`rotulo`)
115
+
116
+ **Quando:** a pessoa diz o nome de uma coisa que a gente quer fixar ("produto digital", "Sistema de IA",
117
+ "um celular", "30 min por dia"). Peça pequena: cabe em tela cheia. Na prova, entra sozinho pelo campo
118
+ `rotulo` do bloco, 0,62 s depois do corte (o whoosh da troca vem primeiro e o pop do rótulo não briga com ele).
119
+
120
+ ```json
121
+ {"id": "tag", "tipo": "rotulo", "texto": "Produto *digital*", "em": "produtos", "depois_de": 20,
122
+ "sai": "Produtos", "sai_depois_de": 21.5}
123
+ ```
124
+
125
+ O `build.py` mede o topo da cabeça na tomada (rosto do `face_layout.json` mais 35% da altura do rosto, para
126
+ o cabelo e o boné) e põe o rótulo 40 px acima. Sem espaço acima, vai para baixo do queixo.
127
+
128
+ ```html
129
+ <div class="rotulo" id="tag" style="left:264px;top:444px;width:551px;height:100px;font-size:46px"><span>Produto <b>digital</b></span></div>
130
+ ```
131
+ ```css
132
+ .rotulo{position:absolute;border-radius:999px;display:flex;align-items:center;justify-content:center;
133
+ font-weight:900;color:#352c40;white-space:nowrap;opacity:0;background:#fffdfd;box-shadow:(cartão)}
134
+ .rotulo b{color:#665080}
135
+ ```
136
+ ```js
137
+ tl.fromTo("#tag",{opacity:0,y:-44},{opacity:1,y:0,duration:0.35,ease:"back.out(2)",immediateRender:false},2.494);
138
+ tl.to("#tag",{opacity:0,y:-28,duration:0.25,ease:"power3.in",immediateRender:false},3.655);
139
+ ```
140
+ Som: `pop` 0,05 s depois da entrada. Cuidado: o `<span>` em volta do texto é obrigatório (texto solto em
141
+ caixa flex perde o espaço antes do `<b>`: sai "Produtodigital").
142
+
143
+ ## 2. Carimbo (`carimbo`)
144
+
145
+ **Quando:** afirmação curta que fecha uma ideia ("MIGALHAS", "PRONTO!", "EM SEGUNDOS"). 1 ou 2 palavras.
146
+ Vermelho para o que é ruim ou negado, `"cor": "verde"` para o que deu certo. Use `esconde_legenda` se o
147
+ carimbo escreve a mesma palavra da fala.
148
+
149
+ ```json
150
+ {"id": "mig", "tipo": "carimbo", "texto": "MIGALHAS", "em": "migalhas", "sai": 9.9, "esconde_legenda": true}
151
+ ```
152
+ ```html
153
+ <div class="carimbo" id="mig" style="left:311px;top:427px;width:458px;height:114px;font-size:64px">MIGALHAS</div>
154
+ ```
155
+ ```css
156
+ .carimbo{position:absolute;display:flex;align-items:center;justify-content:center;border:7px solid #e5484d;
157
+ border-radius:16px;color:#e5484d;font-weight:900;letter-spacing:2px;white-space:nowrap;opacity:0;background:rgba(255,253,253,.94)}
158
+ .carimbo.verde{border-color:#2e9e5b;color:#2e9e5b}
159
+ ```
160
+ ```js
161
+ // cai e bate: vem de cima (1,6 vezes a altura dele) acelerando até o impacto (power4.in) e girando, sem escala
162
+ tl.fromTo("#mig",{opacity:0,y:-182,rotation:-18},{opacity:1,y:0,rotation:-8,duration:0.28,ease:"power4.in",immediateRender:false},8.277);
163
+ // no quadro do impacto a tela treme (o #palco inteiro, nunca o #root)
164
+ tl.fromTo("#palco",{x:0},{keyframes:[{x:-8,duration:0.04},{x:7,duration:0.04},{x:0,duration:0.05}],immediateRender:false},8.557);
165
+ tl.to("#mig",{opacity:0,y:-16,duration:0.2,ease:"power2.in",immediateRender:false},9.7);
166
+ ```
167
+ Som: `impacto` no pouso (entrada + 0,27 s), nível chave. `"tremer": false` desliga a tremida.
168
+
169
+ ## 3. Lista com check ou riscada (`lista_check`)
170
+
171
+ Cada item da `lista_check` cabe em até uns 22 caracteres no 9:16. Texto maior estoura o cartão e a bolinha do X ou do check sai da borda, e o `hf check` não avisa. Divida em dois itens ou encurte.
172
+
173
+ **Quando:** a fala enumera ("espaço, estoque, frete"; "não preciso aparecer, ter seguidores, criar do
174
+ zero"). Cada item entra na SUA palavra. `"marca": "check"` (verde, o que tem) ou `"marca": "x"` (risca o
175
+ texto e bate um X vermelho, o que não precisa). Peça grande: moldura.
176
+
177
+ ```json
178
+ {"id": "sem", "tipo": "lista_check", "titulo": "Sem *custo* de", "marca": "check", "depois_de": 22, "sai": 27.9,
179
+ "itens": [{"texto": "Espaço", "em": "espaço"}, {"texto": "Estoque", "em": "estoque"}, {"texto": "Frete", "em": "frete"}]}
180
+ ```
181
+ ```html
182
+ <div class="lista" id="sem" style="left:150px;top:326px;width:780px">
183
+ <div class="tit" style="font-size:44px;margin-bottom:12px">Sem <b>custo</b> de</div>
184
+ <div class="linha" id="sem-l0" style="height:110px"><span class="txt" style="font-size:44px">Espaço</span>
185
+ <span class="marca ok" id="sem-m0"><svg viewBox="0 0 40 40"><path pathLength="1" d="M8 21 L17 30 L33 11"/></svg></span></div>
186
+ <!-- ...uma .linha por item... -->
187
+ </div>
188
+ ```
189
+ ```css
190
+ .linha{position:relative;display:flex;align-items:center;gap:20px;border-top:3px dashed #e6ddf8;opacity:0}
191
+ .marca{margin-left:auto;width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;opacity:0;flex:none}
192
+ .marca.ok{background:#2e9e5b} .marca.x{background:#e5484d}
193
+ .marca path{fill:none;stroke:#fff;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1}
194
+ .risco{position:absolute;left:-8px;right:-8px;top:50%;height:8px;margin-top:-4px;border-radius:4px;background:#e5484d;opacity:0}
195
+ ```
196
+ ```js
197
+ tl.fromTo("#sem",{opacity:0,y:64},{opacity:1,y:0,duration:0.35,ease:"back.out(2)",immediateRender:false},23.625);
198
+ // por item: linha desliza na palavra, a marca bate 0,25 s depois e o traço se desenha
199
+ tl.fromTo("#sem-l0",{opacity:0,x:-40},{opacity:1,x:0,duration:0.3,ease:"back.out(2)",immediateRender:false},23.975);
200
+ // a bolinha bate: cai acelerando e se abre do meio para fora (clip-path circle), sem escala
201
+ tl.fromTo("#sem-m0",{opacity:0,y:-26,clipPath:"circle(20% at 50% 50%)"},{opacity:1,y:0,clipPath:"circle(71% at 50% 50%)",duration:0.18,ease:"power4.in",immediateRender:false},24.225);
202
+ tl.fromTo("#sem-m0 path",{strokeDashoffset:1},{strokeDashoffset:0,duration:0.25,ease:"power2.out",immediateRender:false},24.275);
203
+ // com "marca": "x", também o risco crescendo da esquerda:
204
+ // tl.fromTo("#sem-r0",{opacity:1,clipPath:"inset(0% 100% 0% 0%)"},{clipPath:"inset(0% 0% 0% 0%)",duration:0.22,ease:"power2.out",immediateRender:false},24.275);
205
+ tl.to("#sem",{opacity:0,y:-28,duration:0.25,ease:"power3.in",immediateRender:false},27.65);
206
+ ```
207
+ Som: `pop` no cartão; `tique` (ou `risco`) em cada marca. `pathLength="1"` no path deixa o traço de 0 a 1
208
+ sem medir o comprimento. Item cuja palavra cai depois da saída sai da lista, com aviso.
209
+
210
+ ## 4. Passo a passo (`passo_a_passo`)
211
+
212
+ **Quando:** a pessoa descreve etapas em ordem ("copiei, colei, ativei"). Caixas numeradas uma embaixo da
213
+ outra, seta se desenhando entre elas, e um carimbo final opcional ("PRONTO!").
214
+
215
+ ```json
216
+ {"id": "passos", "tipo": "passo_a_passo", "depois_de": 28,
217
+ "passos": [{"texto": "Você posta", "em": "preocupar"}, {"texto": "Cliente compra", "em": "entregar"}, {"texto": "Recebe na hora", "em": "recebe"}],
218
+ "final": {"texto": "PRONTO!", "em": "automática"}}
219
+ ```
220
+ ```html
221
+ <div id="passos">
222
+ <div class="passo" id="passos-p0" style="left:240px;top:321px;width:600px;height:118px"><span class="n">1</span><b style="font-size:48px">Você posta</b></div>
223
+ <svg class="seta" id="passos-s1" style="left:520px;top:439px;width:40px;height:52px" viewBox="0 0 40 52"><path pathLength="1" d="M20 4 L20 44 M9 33 L20 46 L31 33"/></svg>
224
+ <div class="passo" id="passos-p1" ...>...</div>
225
+ </div>
226
+ ```
227
+ ```js
228
+ tl.fromTo("#passos-p0",{opacity:0,x:-60},{opacity:1,x:0,duration:0.35,ease:"back.out(2)",immediateRender:false},29.375);
229
+ tl.fromTo("#passos-s1",{opacity:0},{opacity:1,duration:0.05,immediateRender:false},30.005);
230
+ tl.fromTo("#passos-s1 path",{strokeDashoffset:1},{strokeDashoffset:0,duration:0.3,immediateRender:false},30.005); // seta antes do passo
231
+ tl.fromTo("#passos-p1",{opacity:0,x:-60},{opacity:1,x:0,duration:0.35,ease:"back.out(2)",immediateRender:false},30.355);
232
+ tl.to("#passos > *",{opacity:0,y:-20,duration:0.2,stagger:0.03,ease:"power3.in",immediateRender:false},31.9);
233
+ ```
234
+ Som: `clique` em cada passo, `impacto` no carimbo final.
235
+
236
+ ## 5. Número contando (`contador`)
237
+
238
+ **Quando:** a fala dá um número que importa: quantidade ("3 formatos", "40 produtos"), tempo ("30 dias"),
239
+ preço.
240
+
241
+ ```json
242
+ {"id": "cont", "tipo": "contador", "valor_inicial": 0, "valor_final": 3, "sufixo": " formatos",
243
+ "rotulo": "feitos pela IA", "em": "e-books", "sai": 45.8, "duracao": 0.9, "casas": 0}
244
+ ```
245
+ ```html
246
+ <div class="contador" id="cont" style="left:286px;top:394px;width:507px;height:250px">
247
+ <div class="num" style="font-size:120px"><i></i><span id="cont-v">&#8203;0</span><i> formatos</i></div>
248
+ <div class="rot">feitos pela IA</div></div>
249
+ ```
250
+ ```js
251
+ tl.fromTo("#cont",{opacity:0,y:60},{opacity:1,y:0,duration:0.35,ease:"back.out(2)",immediateRender:false},43.395);
252
+ // um valor por quadro (dados + laço): o seek em qualquer quadro mostra o número certo
253
+ for (const [t, p] of [[43.6,{textContent:"​0"}],[43.633,{textContent:"​0"}],[43.666,{textContent:"​1"}] /* ... */]) tl.set("#cont-v", p, t);
254
+ tl.fromTo("#cont",{y:0},{keyframes:[{y:-14,duration:0.12,ease:"power2.out"},{y:0,duration:0.25,ease:"power2.in"}],immediateRender:false},44.495); // pousa: um pulinho
255
+ ```
256
+ O valor sobe com `1 - (1 - u)^3` (rápido no começo, assenta no fim), formatado em pt-BR (1.250,5). Som:
257
+ `contagem` no começo e `impacto` quando pousa. O texto nasce com o valor inicial no HTML e com o espaço de
258
+ largura zero na frente (pegadinhas 2 e 3).
259
+
260
+ ## 6. Notificação no celular (`notificacao`)
261
+
262
+ **Quando:** a fala conta que vendas ou mensagens chegam ("as vendas começam a cair"). Um celular sobe e as
263
+ notificações caem uma por palavra ("Nova venda", "Pedido aprovado"). Peça grande (400 x 700): moldura.
264
+
265
+ ```json
266
+ {"id": "cel", "tipo": "notificacao", "titulo": "Hoje", "depois_de": 33, "sai": 40.3,
267
+ "notas": [{"texto": "Nova venda", "em": "custo"}, {"texto": "Nova venda", "em": "produção"}]}
268
+ ```
269
+ ```html
270
+ <div class="cel" id="cel" style="left:340px;top:237px;width:400px;height:700px"><div class="tela"><div class="hoje">Hoje</div>
271
+ <div class="nt" id="cel-n0"><span class="ic">(sacola em svg)</span><b>Nova venda</b><span class="ok">(check em svg)</span></div>
272
+ </div></div>
273
+ ```
274
+ ```js
275
+ tl.fromTo("#cel",{opacity:0,y:120},{opacity:1,y:0,duration:0.5,ease:"back.out(1.5)",immediateRender:false},33.925);
276
+ tl.fromTo("#cel-n0",{opacity:0,y:-80},{opacity:1,y:0,duration:0.3,ease:"back.out(2)",immediateRender:false},34.425);
277
+ tl.to("#cel",{opacity:0,y:60,duration:0.3,ease:"power3.in",immediateRender:false},40.0);
278
+ ```
279
+ Som: `whoosh` quando o celular sobe, `ding` na primeira notificação (chave), `pop` nas outras.
280
+
281
+ ## 7. Foto em polaroide (`polaroide`)
282
+
283
+ **Quando:** a pessoa cita algo real dela ("meu carro", "minhas viagens", "meu produto"). Só foto
284
+ VERDADEIRA da pessoa, copiada para `assets/`. Nada de foto de banco fingindo ser dela. Uma ou duas fotos.
285
+
286
+ ```json
287
+ {"id": "fotos", "tipo": "polaroide", "sai": 52.2,
288
+ "fotos": [{"arquivo": "assets/carro.jpg", "legenda": "meu carro", "em": "carro", "foco": "50% 72%"},
289
+ {"arquivo": "assets/viagem.jpg", "legenda": "minhas viagens", "em": "viagem"}]}
290
+ ```
291
+ `foco` é o `object-position` da foto (onde fica o assunto dentro do quadrado).
292
+ ```html
293
+ <div class="pola" id="fotos-f0" style="left:150px;top:335px;width:330px;height:420px;padding:16px 16px 0">
294
+ <img src="assets/carro.jpg" alt="" style="width:298px;height:330px;object-position:50% 72%"/><span style="font-size:46px;line-height:67px">meu carro</span></div>
295
+ ```
296
+ ```js
297
+ // cai girando e assenta torta: -6° a primeira, +5° a segunda
298
+ tl.fromTo("#fotos-f0",{opacity:0,y:-120,rotation:-15.6},{opacity:1,y:0,rotation:-6,duration:0.4,ease:"back.out(1.7)",immediateRender:false},47.095);
299
+ tl.to("#fotos > .pola",{opacity:0,y:-40,duration:0.25,stagger:0.05,ease:"power3.in",immediateRender:false},51.9);
300
+ ```
301
+ Som: `camera` 0,12 s depois de cada foto. Legenda na Caveat (letra de mão).
302
+
303
+ ## 8. Logo ou objeto preso na mão (`objeto_na_mao`)
304
+
305
+ **Quando:** a pessoa fala de uma coisa e "segura" ela no ar ("posso colocar aqui a logo e ela vai
306
+ acompanhar"). A imagem (PNG com fundo transparente) nasce na mão e segue a palma quadro a quadro, com um
307
+ atraso curto de mola. Precisa do rastreio das mãos antes:
308
+
309
+ `uv run --python 3.12 --with mediapipe==0.10.21 --with numpy python ferramentas/rastrear_maos.py --edit '<EDIT>' --de 19 --ate 22`
310
+
311
+ Versão fixa: o mediapipe 1.x (que o uv pega sem versão, no Python 3.13) aborta com "Service is unavailable". A
312
+ primeira vez o uv baixa o Python 3.12 e o mediapipe, uns minutos: avise a pessoa antes. Depois fica guardado.
313
+
314
+ ```json
315
+ {"id": "logo", "tipo": "objeto_na_mao", "imagem": "assets/logo.png", "mao": "esquerda_da_imagem",
316
+ "em": 19.2, "sai": 21.6, "tamanho": 170, "folga": 30}
317
+ ```
318
+ `mao`: `esquerda_da_imagem`, `direita_da_imagem` ou `qualquer` (o lado é o da IMAGEM; o rótulo Left/Right
319
+ do MediaPipe engana). O objeto fica `folga` px acima dos nós dos dedos (a palma costuma encostar na borda
320
+ de baixo). Use em tela cheia, dentro de uma tomada só (o rastreio recomeça em cada corte).
321
+ ```html
322
+ <div class="namao" id="logo"><div class="pop" id="logo-p" style="left:-85px;top:-85px;width:170px;height:170px"><img src="assets/logo.png" alt=""/></div></div>
323
+ ```
324
+ ```js
325
+ // posição por quadro: mola (frequência 3,4 Hz, amortecimento 0,45) calculada no build a partir da palma; inclina com a velocidade
326
+ for (const [t, p] of [[19.2,{x:622.7,y:930.5,rotation:0}],[19.233,{x:620.9,y:930.6,rotation:0.53}] /* ... */]) tl.set("#logo", p, t);
327
+ // nasce da mão: um círculo que se abre do meio (a sombra cabe no círculo aberto) e sobe um pouco, sem escala
328
+ tl.fromTo("#logo-p",{opacity:0,y:24,clipPath:"circle(12% at 50% 50%)"},{opacity:1,y:0,clipPath:"circle(100% at 50% 50%)",duration:0.4,ease:"back.out(2)",immediateRender:false},19.2);
329
+ tl.to("#logo-p",{opacity:0,clipPath:"circle(12% at 50% 50%)",duration:0.18,ease:"power2.in",immediateRender:false},21.42);
330
+ ```
331
+ A posição da tela considera o enquadramento da câmera naquele quadro (zoom e deslocamento do `#inner`).
332
+ Quadro sem mão segura a última posição (nunca pisca). Som: `pop` na entrada (chave) e na saída. Só logo que
333
+ a pessoa tem direito de usar (a dela, ou a de uma ferramenta citada, sem parecer anúncio da marca).
334
+
335
+ ## 9. Painel "editando ao vivo" com timecode (`hud`)
336
+
337
+ **Quando:** vídeo que mostra o próprio processo (a IA editando, um bastidor com etapas). Pílula escura à
338
+ esquerda com bolinha vermelha piscando, "EDITANDO AO VIVO" e o timecode do quadro (MM:SS:QQ); pílula à
339
+ direita com a etapa ("01 CORTES"). No fim pode virar "EDIÇÃO CONCLUÍDA" com a bolinha verde.
340
+
341
+ ```json
342
+ {"id": "hud", "tipo": "hud", "em": 0.3, "sai": 9.9, "concluido_em": 9.3, "tamanho": 1.4, "topo": 240,
343
+ "secoes": [{"em": 0.4, "nome": "01 GANCHO"}, {"em": "migalhas", "nome": "02 IMPACTO"}]}
344
+ ```
345
+ `tamanho` multiplica a pílula inteira (1 = 52 px de altura com letra de 21 px; 1,4 lê bem no celular, onde o
346
+ tamanho 1 é o menor texto do vídeo). `topo` é o y da pílula (padrão 160 no 9:16, 40 no 16:9).
347
+ ```html
348
+ <div id="hud"><div class="pill l"><i id="hud-dot"></i><b id="hud-state">Editando ao vivo</b><span id="hud-tc">00:00:00</span></div>
349
+ <div class="pill r" id="hud-r"><span id="hud-n">&#8203;01</span><b id="hud-name">GANCHO</b></div></div>
350
+ ```
351
+ ```css
352
+ #hud .pill{position:absolute;top:0;height:52px;border-radius:26px;background:rgba(11,8,25,.88);border:1.5px solid #49445d;
353
+ display:flex;align-items:center;font-family:Mono;font-weight:700;font-size:21px;letter-spacing:.13em;text-transform:uppercase;white-space:nowrap}
354
+ ```
355
+ ```js
356
+ tl.fromTo("#hud-dot",{opacity:1},{opacity:0.15,duration:0.55,ease:"sine.inOut",yoyo:true,repeat:15,immediateRender:false},0.3); // repeat ímpar: termina aceso
357
+ for (const [t, p] of [[0.3,{textContent:"00:00:09"}],[0.333,{textContent:"00:00:10"}] /* ... um por quadro */]) tl.set("#hud-tc", p, t);
358
+ tl.set("#hud-n",{textContent:"​02"},8.337); // sem o ​ o GSAP escreve "2"
359
+ tl.set("#hud-name",{textContent:"IMPACTO"},8.337);
360
+ // a pílula abre da direita para a esquerda (recorte com a borda redonda)
361
+ tl.fromTo("#hud-r",{opacity:0,clipPath:"inset(0% 0% 0% 22% round 36.4px)"},{opacity:1,clipPath:"inset(0% 0% 0% 0% round 36.4px)",duration:0.16,ease:"power2.out",immediateRender:false},8.357);
362
+ tl.set("#hud-state",{textContent:"Edição concluída"},9.3);
363
+ tl.set("#hud-dot",{backgroundColor:"#3bc59d",boxShadow:"0 0 12px #3bc59d",opacity:1},9.3);
364
+ ```
365
+ Fonte mono em arquivo (`assets/fontes/JetBrainsMono.ttf`). O painel fica em y 160 de propósito (acima da
366
+ zona segura, onde o Reels põe o nome do perfil): se a pessoa achar que some ou que está pequeno, use
367
+ `"topo": 240` e `"tamanho": 1.4`. Som: `clique` a cada etapa, `sucesso` no concluído. Timecode sem
368
+ `repeat: -1`: um valor por quadro; no fim parado (`segura_fim`) ele fica no último quadro do vídeo.
369
+
370
+ ## 10. Texto atrás da pessoa (`atras_da_pessoa`, 2,5D)
371
+
372
+ **Quando:** uma palavra forte que merece presença em volta da pessoa, num momento de ênfase ("O FIM",
373
+ "EDITOU", "GRÁTIS"): a palavra fica grande atrás dela, a cabeça passa na frente da parte de baixo das letras e
374
+ a palavra desliza devagar de lado (paralaxe leve). É composição em planos, 2,5D (vídeo de fundo, texto, pessoa
375
+ recortada na frente), não 3D. Uma palavra por vez, curta (até uns 10 letras), só em tela cheia, e o vídeo
376
+ volta ao normal depois dela.
377
+
378
+ **Antes:** o recorte da pessoa daquele trecho, aprovado num teste de uns 5 s (cabelo, mãos, borda):
379
+ `python3 ferramentas/pessoa_recortada.py --edit '<EDIT>' --de 5.5 --ate 9.5`. Sem recorte, ou com o recorte de
380
+ outro trecho, o build para com o comando certo.
381
+
382
+ ```json
383
+ {"id": "editou", "tipo": "atras_da_pessoa", "texto": "EDITOU", "em": "editou", "sai": "foi", "sai_depois_de": 7.5}
384
+ ```
385
+
386
+ Campos: `texto` (`*parte*` sai na cor de destaque), `em`, `sai`, `altura` (`cabeca`, o padrão; `topo`; ou o
387
+ topo do texto em pixels), `tamanho` (px; padrão: a maior que cabe na zona segura, até 16% do lado maior do
388
+ quadro), `cor` (padrão: o papel do estilo), `paralaxe` (1; 0 desliga), `caixa_alta` (true), `recorte`
389
+ (`assets/pessoa.webm`; use outro arquivo, com `--saida`, quando o vídeo tem tela verde em outro trecho),
390
+ `esconde_legenda`, `nome`, `frente` e `fundo_anda` (os dois abaixo).
391
+
392
+ **Terceiro plano (`"frente"`).** Uma imagem do projeto na frente da pessoa, para o 2,5D ter os três planos:
393
+ fundo, pessoa com a palavra atrás, objeto na frente.
394
+
395
+ ```json
396
+ {"id": "editou", "tipo": "atras_da_pessoa", "texto": "EDITOU", "em": "editou", "sai": 7.0,
397
+ "frente": {"imagem": "assets/tesoura.png", "em": "não"}}
398
+ ```
399
+
400
+ `frente` aceita só o caminho (`"assets/tesoura.png"`) ou `{"imagem", "em", "sai", "lado", "tamanho", "onde"}`. Ela
401
+ entra depois da palavra (padrão 0,35 s depois; nunca antes de 0,2 s: um elemento por vez), num canto de baixo, do
402
+ lado oposto ao rosto (`"lado": "esquerda"` ou `"direita"` escolhe), com 26% do lado menor da tela de largura
403
+ (`tamanho` em px), e sai com a palavra. PNG com fundo transparente; jpg aparece como retângulo e o build avisa. O
404
+ build também avisa quando ela passa na frente do rosto, e a legenda desvia dela. Fica numa sub-composição própria
405
+ por cima de tudo (faixa "Frente · tesoura.png"):
406
+
407
+ ```html
408
+ <div class="frente" id="editou-frente" style="left:659px;top:1249px;width:281px;height:281px">
409
+ <div class="fr2" id="editou-frente-in"><img src="assets/tesoura.png" alt=""/></div></div>
410
+ ```
411
+ ```js
412
+ // entra com um pulo; a caixa de fora anda mais que a palavra (±40 px contra ±18), sem escala
413
+ tl.fromTo("#editou-frente-in",{opacity:0,y:110},{opacity:1,y:0,duration:0.4,ease:"back.out(1.6)",immediateRender:false},0);
414
+ tl.fromTo("#editou-frente",{x:-40,y:8},{x:40,y:-8,duration:0.717,ease:"none",immediateRender:false},0);
415
+ ```
416
+
417
+ **O fundo anda um pouco (`"fundo_anda"`, ligado por padrão).** Cada plano tem o seu deslocamento: o fundo menos, a
418
+ palavra mais, a frente mais ainda. O fundo é a gravação, e a pessoa recortada sai do mesmo vídeo: os dois andam
419
+ juntos (se só um andasse, apareceria a borda dupla). O build põe o `#inner` e o `#innerp` cada um dentro de uma
420
+ caixa `.plfundo` e mexe nas duas com o mesmo tween, na timeline principal:
421
+
422
+ ```js
423
+ tl.fromTo(".plfundo",{x:0,scale:1},{x:6,scale:1.015,duration:1.487,ease:"sine.inOut",immediateRender:false},5.48);
424
+ tl.set(".plfundo",{x:0,scale:1},7.067); // volta ao normal: no corte, seco; sem corte perto, em 0,5 s
425
+ ```
426
+
427
+ A aproximação de 1,5% nunca mostra borda (o vídeo cresce a partir do meio). `"fundo_anda": false` deixa o fundo
428
+ parado; dois textos atrás da pessoa ao mesmo tempo: só o primeiro mexe no fundo (o build avisa).
429
+
430
+ O `build.py` põe o texto numa sub-composição entre o vídeo e a pessoa, e a pessoa recortada numa segunda
431
+ câmera (`#innerp`) que recebe o mesmo enquadramento do vídeo (a classe `.camera` anda junto em todos os
432
+ cortes):
433
+
434
+ ```html
435
+ <div id="frame">
436
+ <div id="inner" class="camera"><video id="cam" ...></video></div>
437
+ <div id="atras">
438
+ <div id="faixa-editou" class="faixa" data-composition-id="peca-editou" data-composition-src="compositions/editou.html"
439
+ data-start="1.14" data-duration="1.84" data-track-index="10" data-timeline-label="Texto atrás · EDITOU" ...></div>
440
+ </div>
441
+ <div id="innerp" class="camera">
442
+ <video id="pa-editou" class="pessoa-atras" src="assets/pessoa.webm" muted playsinline data-start="1.04" data-duration="2.04"
443
+ data-media-start="0.54" data-track-index="40" data-timeline-label="Pessoa recortada · EDITOU"></video>
444
+ </div>
445
+ </div>
446
+ ```
447
+ ```html
448
+ <!-- compositions/editou.html: uma letra por span, o acento junto da letra -->
449
+ <div class="atras-txt" id="editou" style="left:140px;top:324px;width:800px;height:171px;font-size:171px;color:#fffdfd">
450
+ <span class="atl" id="editou-l0">E</span><span class="atl" id="editou-l1">D</span>...</div>
451
+ ```
452
+ ```css
453
+ .atras-txt{position:absolute;display:flex;align-items:center;justify-content:center;font-weight:900;line-height:1;
454
+ white-space:nowrap;letter-spacing:-.01em;text-shadow:0 14px 40px rgba(0,0,0,.28)}
455
+ .atras-txt .atl{display:inline-block;opacity:0}
456
+ .atras-txt .atl.d{color:#665080}
457
+ ```
458
+ ```js
459
+ // letras sobem uma a uma na palavra falada
460
+ tl.fromTo("#editou .atl",{opacity:0,yPercent:70},{opacity:1,yPercent:0,duration:0.45,ease:"power3.out",stagger:0.05,immediateRender:false},0);
461
+ // paralaxe: a palavra desliza de lado enquanto a pessoa fica parada na frente (sem escala)
462
+ tl.fromTo("#editou",{x:-18,y:6},{x:18,y:-6,duration:1.74,ease:"none",immediateRender:false},0);
463
+ tl.to("#editou .atl",{opacity:0,yPercent:-35,duration:0.28,ease:"power2.in",stagger:0.02,immediateRender:false},1.36);
464
+ ```
465
+
466
+ - **Onde:** centrado na cabeça e com o meio das letras no topo da cabeça, medido no próprio recorte
467
+ (`top_median` e `head_cx` do `pessoa.json`, por tomada). A cabeça esconde só a parte de baixo do miolo da
468
+ palavra; a metade de cima continua à vista.
469
+ - **Legível:** o build avisa quando o rosto medido (ou o corpo, abaixo do queixo) cobre mais de uns 40% da
470
+ largura das letras, contando só a letra que fica mais da metade atrás da pessoa. Suba o texto
471
+ (`"altura": "topo"`), use uma palavra mais comprida ou outro trecho.
472
+ - **Tempo:** a pessoa recortada entra 0,1 s antes da primeira letra e sai 0,1 s depois da última, sempre dentro
473
+ do trecho que o recorte cobre.
474
+ - **Conferir:** amostra de uns 5 s com a palavra chegando, a pessoa se mexendo e a palavra saindo; olhe a borda
475
+ do cabelo e a palavra no arquivo renderizado.
476
+
477
+ Som: `whoosh` na entrada; `pop` na frente.
478
+
479
+ ## 11. Cartão final com botão (`cartao_final`)
480
+
481
+ **Quando:** sempre que o vídeo termina com chamada (anúncio). Entra no último quadro da fala e fica 1,8 s
482
+ depois dela: papel do estilo, logo (opcional), título, frase à mão e o botão, que pulsa uma vez. Vai no topo
483
+ do `projeto.json`, fora de `animacoes`, e só aparece no `completo` (ou numa amostra que chega ao fim).
484
+
485
+ Sem chamada, mas com o fim precisando de tempo de leitura (a síntese é a última palavra, o diagrama tem que
486
+ ficar inteiro uns 2 s, o carimbo acabou de bater): `"segura_fim": 1.2` no topo do `projeto.json`. O último
487
+ quadro fica parado, a voz já terminou e as peças e cenas continuam até o `sai` delas. Não deixe silêncio no
488
+ corte para isso (o corte continua seco). Com os dois ligados, o cartão entra depois do fim parado.
489
+
490
+ ```json
491
+ "cartao_final": {"ligado": true, "segundos": 1.8, "titulo": "Conheça o Jarvis", "sub": "aula gratuita",
492
+ "botao": "Toque em Saiba Mais", "logo": "assets/logo.png"}
493
+ ```
494
+ ```html
495
+ <div id="fim"><div class="logo" id="fim-logo" style="background-image:url(assets/logo.png);left:260px;top:523px;width:560px;height:300px"></div>
496
+ <div class="tit" id="fim-tit" style="top:883px;font-size:66px">Conheça o Jarvis</div>
497
+ <div class="sub" id="fim-sub" style="top:973px;font-size:64px">aula gratuita</div>
498
+ <div class="bt" id="fim-bt" style="left:190px;top:1093px;width:700px;height:140px;font-size:52px">Toque em Saiba Mais</div></div>
499
+ ```
500
+ ```js
501
+ tl.fromTo("#fim",{opacity:0},{opacity:1,duration:0.25,immediateRender:false},53.733); // 0,1 s antes do fim da fala
502
+ tl.fromTo("#fim-bt",{opacity:0,y:30},{opacity:1,y:0,duration:0.4,ease:"back.out(2)",immediateRender:false},54.183);
503
+ tl.fromTo("#fim-bt",{y:0},{keyframes:[{y:-12,duration:0.3,ease:"power2.out"},{y:0,duration:0.4,ease:"power2.in"}],immediateRender:false},54.733); // pulsa: um pulinho
504
+ ```
505
+ A logo vai como fundo de uma `div` (a mesma imagem em dois `<img>` sem tempo faz o lint avisar de mídia
506
+ duplicada). O botão diz o mesmo que o botão do anúncio. Som: `whoosh` na entrada, `pop` no botão.
507
+
508
+ ## 12. Lettering com ordem de leitura (`lettering`)
509
+
510
+ **Quando:** a fala tem uma a quatro expressões que mudam o jeito de entender o vídeo e merecem ficar grandes na
511
+ tela, com um peso para cada papel (`references/lettering.md`: o que escrever, os três papéis, o estilo aprovado). A
512
+ peça cuida da ordem, do tempo, do encaixe e da animação; quem escolhe o texto e o momento é você.
513
+
514
+ ```json
515
+ {"id": "melhor", "tipo": "lettering", "principal": "Ficou com a *melhor*", "apoio": "sem regravar nada",
516
+ "chamada": "Veja o resultado", "em": "ficou", "sai": 21.2, "posicao": "topo", "estilo": "bloco"}
517
+ ```
518
+
519
+ | campo | o que é |
520
+ |---|---|
521
+ | `principal` | o texto que manda (obrigatório). `*parte*` sai na cor de destaque. Até 3 palavras grandes por vez; de 4 a 6 viram dois momentos; mais de 6 é erro |
522
+ | `apoio` | opcional: o complemento curto, com metade do tamanho, peso 800, palavra por palavra |
523
+ | `chamada` | opcional: a pílula do convite, por último |
524
+ | `em`, `sai` | palavra ou segundo, como nas outras peças (`depois_de`, `sai_depois_de`). `sai` no fim do corte: fica até o último quadro |
525
+ | `antecipa` | quanto cada palavra pode aparecer antes de ser dita: padrão 0,05 s, no máximo 0,15 |
526
+ | `posicao` | `topo`, `meio` ou `base`. Sem ela, o build tenta topo, base e meio, nessa ordem, e fica com a primeira que não cobre o rosto. Na moldura vai na área livre |
527
+ | `estilo` | `bloco` (Montserrat 900 em caixa alta, branca com contorno forte e o destaque na cor base do estilo) ou `manuscrito` (Caveat 700 à mão, destaque amarelo) |
528
+ | `apoio_em`, `chamada_em` | opcionais: a palavra de cada um. Nunca antes do elemento de cima terminar de chegar (o build empurra e avisa) |
529
+ | `tamanho` | teto da letra do principal em px (padrão 13% do lado menor no bloco, 16% no manuscrito) |
530
+ | `caixa_alta`, `esconde_legenda` | padrão: caixa alta no bloco; a legenda some no trecho (`false` mantém tudo) |
531
+ | `legenda_junto` | a legenda continua, mas sem as palavras que a peça escreve grande naquele instante. Padrão: `true` com a legenda `discreta`, `false` com a `padrao` (letra grande em cima e legenda grande embaixo brigam) |
532
+
533
+ O que o build faz, sem você escrever nada:
534
+
535
+ - **Ordem de leitura.** O principal chega primeiro e é o maior, o mais pesado e o de mais contraste. O apoio só
536
+ começa 0,12 s depois de o principal terminar de chegar; a chamada, por último. No `bloco`, a batida (a linha
537
+ dá um pulinho para cima, 7% da letra, e volta) marca o fim da chegada do principal.
538
+ - **Tempo da fala, palavra por palavra.** Cada palavra do principal é procurada na fala, em ordem, a partir do
539
+ `em` (a muleta que o texto cortou pode ficar no meio), e entra na hora em que é dita, antecipando no máximo o
540
+ `antecipa` (0,15 s): o começo medido na onda quando existe o `palavras_onda.json` (o Whisper adianta até 0,3 s;
541
+ sem o arquivo, o build avisa). As letras dela aparecem dentro do tempo em que ela é dita (0,1 a 0,3 s). O apoio
542
+ também, palavra por palavra, nunca antes de o principal terminar de chegar. Palavra que não está na fala (texto
543
+ encurtado) vem logo depois da anterior. O que precisa aparecer junto (número com a unidade, a negação com a
544
+ palavra de antes) aparece na hora da última delas: nada antes de ser dito.
545
+ - **Letra por letra montada no lugar.** Cada letra é um grafema (o acento junto: "NÃO" são três letras, N, Ã e O).
546
+ A linha inteira já está no HTML, com as letras invisíveis, na posição final: nada se recentraliza enquanto
547
+ aparece. O espaço entre palavras é margem em px (o HyperFrames joga fora o texto que é só espaço).
548
+ - **Nenhum quadro muda o sentido.** Número aparece inteiro, junto com "R$", "mil", "%" (nunca "R$ 1" antes de
549
+ "R$ 1.000"). A negação que não tem outra antes dela aparece junto com a palavra de antes ("PRECISA NÃO" nunca
550
+ mostra "PRECISA" sozinho); depois de um "não", a frase já é negativa e o resto vem letra por letra.
551
+ - **Encaixe por procedimento.** A largura é medida com a fonte de verdade, no peso usado
552
+ (`assets/fontes/larguras.json`). A ordem: 1 a 3 linhas quebrando por sentido (nunca artigo ou preposição longe do
553
+ nome, número longe da unidade, "não" longe do verbo); mais de 3 palavras viram dois momentos (duas telas: o
554
+ primeiro sai inteiro antes de o segundo entrar, na palavra dele); a letra só diminui por último, até caber numa
555
+ área livre sem cobrir o rosto. Abaixo da legenda mais 12 px (72 px no 9:16) não é lettering: erro com o que
556
+ encurtar.
557
+ - **Fonte carregada antes de medir.** Na página, a timeline da peça é montada depois do `document.fonts.ready` e
558
+ registrada no fim; antes disso, cada linha é medida no navegador e, se passar da largura, a letra desce o que
559
+ falta.
560
+ - **Legenda fora, ou junto.** A legenda some do `em` ao `sai`. Com `legenda_junto` (o padrão com a legenda
561
+ `discreta`), só as palavras que a peça escreve grande saem da legenda; o resto da fala continua legendado ali
562
+ (sem isso, uns 30% da fala ficavam sem texto na tela). Se a peça não acha nem metade do principal na fala, a
563
+ legenda sai do trecho inteiro, com aviso.
564
+ - **Tempo de leitura.** Depois de tudo completo, pelo menos 1 s até 4 palavras e mais 0,3 s por palavra: menos
565
+ que isso vira aviso. Quando a próxima expressão entra logo depois (fala rápida), atrasar o `sai` não resolve e o
566
+ aviso diz para tirar uma expressão ou juntar esta com a próxima numa peça só (a segunda vira o apoio). Cada
567
+ expressão ocupa uns 2 s (chegar, 1 s de leitura e sair): em 10 s de fala rápida cabem 3.
568
+ - **Até o último quadro.** Com o `sai` no fim do corte (até 0,25 s antes), a peça não tem saída: fica legível até o
569
+ último quadro do vídeo, também no quadro parado do `segura_fim`.
570
+ - **Tamanho da letra.** A linha `nota:` do build diz a letra usada e por que não foi maior (a largura da área ou o
571
+ rosto). O `tamanho` é só o teto.
572
+
573
+ ```html
574
+ <!-- compositions/errei.html: uma linha por div, uma palavra por .lt-w, uma letra por .lt-u (invisível até a vez dela) -->
575
+ <div class="lt-m" id="errei-m0">
576
+ <div class="lt-l lt-p" id="errei-m0l0" style="left:164px;top:1115px;width:752px;height:109px;line-height:109px;
577
+ font-size:92px;-webkit-text-stroke:8px #352c40"><span class="lt-w"><span class="lt-u" id="errei-m0u0">E</span>...</span></div>
578
+ <div class="lt-l lt-a" id="errei-al0" ...><span class="lt-w"><span class="lt-u" id="errei-a0">e</span></span>...</div>
579
+ </div>
580
+ ```
581
+ ```js
582
+ const montar = () => {
583
+ ajustar(); // mede cada linha com a fonte carregada; passou da largura, a letra desce
584
+ const tl = gsap.timeline({ paused: true });
585
+ tl.fromTo("#errei-m0u0",{opacity:0,yPercent:55},{opacity:1,yPercent:0,duration:0.24,ease:"power3.out",immediateRender:false},0);
586
+ // ... uma letra a cada 18 a 50 ms; letras que aparecem juntas (número, negação) vão no mesmo tween: "#x-u3,#x-u4"
587
+ window.__timelines["peca-errei"] = tl; // registrada só no fim
588
+ };
589
+ document.fonts.ready.then(montar);
590
+ ```
591
+
592
+ Som: `pop` na batida do principal (pequeno, -18; na última peça lettering do vídeo, a conclusão, médio, -15),
593
+ `whoosh` na entrada do manuscrito, `pop` baixo (-20) no pouso do apoio e `pop` pequeno na chamada. O
594
+ `plano_sfx.py --projeto` usa esses níveis.
595
+
596
+ ---
597
+
598
+ ## Pegadinhas do HyperFrames e do GSAP
599
+
600
+ Todas já custaram um render refeito. O `build.py` já segue; se escrever peça à mão, siga também.
601
+
602
+ 1. **Nada em tempo negativo.** Uma legenda que começava 0,05 s antes da amostra entrou em -0,05 e o GSAP
603
+ empurrou a timeline INTEIRA 0,05 s para frente: a logo presa na mão ficou 1,5 quadro atrás da mão.
604
+ Toda posição passa por `max(0, t - inicio)`.
605
+ 2. **Texto que muda nunca nasce vazio.** Um `tl.set(el, {textContent: "A"}, 1)` sobre um elemento vazio
606
+ não volta a ficar vazio quando o seek vai para antes de 1 s (testado no GSAP 3.14.2). Escreva o primeiro
607
+ valor no HTML.
608
+ 3. **'01' vira 1.** O GSAP lê texto numérico como número. Prefixe com o espaço de largura zero:
609
+ `{textContent: "​01"}`.
610
+ 4. **Estado do quadro 0 com fromTo curto.** `tl.fromTo(sel, P, {...P, duration: 0.04, ease: "none"}, 0)`
611
+ em vez de `tl.set(sel, P, 0)`: o set em 0 pode sumir quando o seek volta ao começo.
612
+ 5. **`immediateRender: false` depois do primeiro tween** do elemento. Sem isso o `fromTo` de uma saída
613
+ aplica o estado inicial na hora da montagem e a peça some antes de entrar.
614
+ 6. **Anime transform, opacity e clip-path.** Nada de tween em `left`, `top`, `width`, `height`: o lint
615
+ reclama e o movimento trava no pixel inteiro. A moldura é `clip-path: inset(...)` no `#frame`.
616
+ 7. **Nada de `transform` no CSS de um elemento que o GSAP vai mover** (`gsap_css_transform_conflict`).
617
+ O estado inicial vai no `from` do `fromTo` (o risco começa com `{opacity: 1, clipPath: "inset(0% 100% 0% 0%)"}`).
618
+ 8. **Fonte em arquivo, sempre.** `local("SF Mono")` não carrega no navegador do render: o painel saiu com
619
+ outra fonte. Toda `font-family` tem `@font-face` para um arquivo em `assets/fontes/`.
620
+ 9. **Texto solto em caixa `flex` perde o espaço** antes de um `<b>`. Envolva num `<span>`.
621
+ 10. **Por quadro, só com dados.** Timecode, contador e objeto na mão têm um valor por quadro; o tempo de
622
+ cada `set` arredonda PARA BAIXO em milissegundos (nunca cai um quadro depois) e entra como lista + laço.
623
+ Nada de `repeat: -1`, `Date.now()` ou `Math.random()`.
624
+ 11. **Corte dentro da transição da moldura usa o enquadramento da moldura**, e o tempo do corte com 3
625
+ casas pega o rosto da tomada nova com 2 ms de folga (detalhes em `references/enquadramento.md`).
626
+ 12. **Vídeo e áudio:** `<video>` sempre `muted playsinline` e com `id`; a voz vai num `<audio id>` à parte
627
+ com o mesmo `data-media-start`. Nunca `crossorigin`. Nunca `data-start` numa caixa que tem um
628
+ `<video data-start>` dentro. Não anime o `<video>`: anime a caixa em volta.
629
+ 13. **Sons:** vão no campo `sons` do `projeto.json`, que o `helpers/plano_sfx.py --projeto` escreve (nunca
630
+ cole `<audio>` no `index.html`: o build reescreve). Cada `<audio>` com `id` e `data-track-index` próprios.
631
+ O início é `pico_na_tela - pico_no_arquivo + inicio_no_arquivo` (o pico do som cai no quadro do
632
+ movimento; `data-media-start` pula o começo do arquivo); volume `10^((nível - pico_do_trecho)/20)`, no
633
+ máximo 3,98; a descida é `data-fade-out`. Som que começaria antes do 0 perde só a cabeça.
634
+ Com `data-automation` de volume, o `data-volume` é ignorado: o ganho vai nos pontos da curva.
635
+ 14. **A peça sai antes da cena trocar.** Tudo o que está na moldura some antes dela abrir; item cuja
636
+ palavra cai depois da saída fica de fora (senão fica na tela para sempre por cima da cena seguinte).
637
+ 15. **Palavra repetida pelo Whisper numa pausa** ("aqui aqui" a menos de 0,6 s) vira uma só na legenda.
638
+ 16. **Caminho de arquivo a partir da pasta do projeto, também dentro de `compositions/`.** O estúdio serve
639
+ tudo a partir da raiz: `assets/fontes/Montserrat.ttf` funciona, `../assets/...` é erro no lint (e some na
640
+ prévia). Toda sub-composição traz o `@font-face` das fontes que usa.
641
+ 17. **Sub-composição não alcança o `index.html`.** A timeline de uma peça só mexe no que está dentro dela: a
642
+ tremida do carimbo (que mexe no `#palco`) vai na timeline principal. Raiz da peça estilizada por `#root`,
643
+ nunca por uma classe dela.
644
+ 18. **O lint só confere `index.html` e `compositions/`.** Cena sob medida fora de `compositions/cenas/`
645
+ passaria sem lint.
646
+ 19. **Pessoa recortada no estúdio:** o webm com transparência (VP9) toca direito no Chrome. No Safari a
647
+ transparência pode não aparecer (fundo preto por cima do texto). E, na prévia, vídeo e pessoa tocam como
648
+ dois vídeos separados: numa emenda a pessoa pode ficar um quadro atrás. O render junta quadro a quadro e
649
+ não tem isso; o arquivo renderizado é a referência.
650
+ 20. **Espaço entre elementos some.** O HyperFrames descarta o texto que é só espaço entre dois elementos:
651
+ `<span>Não</span> <span>fui</span>` sai "NÃOFUI" no snapshot e no render. Espaço entre palavras em spans
652
+ separados vai como `margin-left` em px (em `em`, um span sem `font-size` mede pelos 16 px da raiz).
653
+ 21. **Risco em cima de texto reprova o `check`** (`text_occluded`, erro), e o `data-layout-allow-occlusion`
654
+ não resolve. O risco vai por baixo das letras: filho do item, com o texto em `position:relative;
655
+ z-index:1` (ou o item com `z-index:0` e o risco com `z-index:-1`). Continua parecendo riscado.
656
+ 22. **Troca de estado com os dois textos cruzando** ("Animações." virando "Efeitos sonoros." no mesmo lugar)
657
+ reprova o `check` (`content_overlap`). O velho sai inteiro antes do novo entrar, ou saem por lados
658
+ opostos sem se cruzar.
659
+ 23. **O estúdio escreve nas cenas próprias.** Ao abrir, ele carimba `data-hf-id` em cada elemento de
660
+ `compositions/cenas/`. Troca de texto exata feita depois disso falha calada; leia o arquivo de novo e
661
+ edite ignorando o `data-hf-id`.
662
+ 24. **As `palavras` da cena mudam** quando o corte é conferido de novo (o Whisper junta ou separa palavras).
663
+ Procure pela palavra e deixe o desenho aguentar uma a mais; nada de uma linha fixa por palavra.
664
+ 25. **Peça sem escala, imagem no tamanho.** Tween de `scale` (ou `scaleX`) deixa o Chrome com o desenho de uma
665
+ escala antiga, que depende do que a página já tocou e de quantos workers o render usou: dois renders do
666
+ mesmo projeto saíam diferentes (medido com todas as peças do catálogo: o texto da pílula do hud, o carimbo, o
667
+ celular; 243 quadros com diferença real entre um render com 1 worker e outro com 5). As peças entram por
668
+ deslocamento, opacidade, giro e `clip-path` (o "cresce" virou recorte que abre ou um pulinho em `y`), sem
669
+ escala; depois disso, os dois renders saem iguais, com diferença só de ruído (1 ou 2 níveis de cor, no
670
+ recorte da pessoa e na borda da moldura). Imagem maior que a caixa, reduzida pelo navegador, também mudava a
671
+ borda (o logo na mão): o build reduz uma cópia para `assets/gerado`, no tamanho em que ela aparece. Só a
672
+ câmera e o fundo do 2,5D (o próprio vídeo) continuam com `scale`.