primocode 10.16.0 → 10.17.1

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 (237) 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 +28 -8
  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/mcp.js +105 -7
  222. package/lib/nuvem.js +11 -1
  223. package/lib/outra-pasta.js +1 -1
  224. package/lib/passagem.js +50 -0
  225. package/lib/perigo.js +29 -12
  226. package/lib/protecao.js +134 -0
  227. package/lib/sandbox.js +11 -4
  228. package/lib/titulo-conversa.js +46 -0
  229. package/lib/tools.js +3 -0
  230. package/lib/ui.js +1 -1
  231. package/package.json +3 -2
  232. package/skills/editar-meu-video/SKILL.md +6 -2
  233. package/skills/editar-video/SKILL.md +113 -0
  234. package/studio/pessoa.py +52 -0
  235. package/voz/primo_voz/app.py +43 -4
  236. package/voz/primo_voz/chat.html +17 -6
  237. package/voz/primo_voz/painel_dados.py +134 -0
@@ -26,10 +26,12 @@ function kBase(comp, P) {
26
26
  function kAmbiente(comp, P, cam) {
27
27
  if (KIT.cenaComElemento || !cam) return;
28
28
  var W = comp.width, H = comp.height, m = Math.min(W, H), ze = kZonaTexto(comp, false), mx = ze.x, my = ze.top, myb = ze.bot, k, colocados = [], R = [], ri;
29
- for (ri = 0; ri < KIT.rects.length; ri++) if (KIT.rects[ri].cena == KIT.cenaIdx) R.push(kRectLayout(KIT.rects[ri]));
29
+ for (ri = 0; ri < KIT.rects.length; ri++) if (KIT.rects[ri].cena == KIT.cenaIdx && !KIT.rects[ri].fora) R.push(kRectLayout(KIT.rects[ri]));
30
30
  var tams = [m * 0.5, m * 0.15];
31
+ // folga do texto: 7% da largura (no mínimo 60 px), pra a câmera que empurra (até 1,1×) não encostar o objeto nas letras
32
+ var fo = Math.max(60, W * 0.07);
31
33
  function livre(x, y, w) {
32
- var x0 = x - w / 2 - 60, x1 = x + w / 2 + 60, y0 = y - w / 2 - 60, y1 = y + w / 2 + 60, i;
34
+ var x0 = x - w / 2 - fo, x1 = x + w / 2 + fo, y0 = y - w / 2 - fo, y1 = y + w / 2 + fo, i;
33
35
  if (x - w / 2 < mx + W * 0.04 || x + w / 2 > W - mx - W * 0.04 || y - w / 2 < my + W * 0.04 || y + w / 2 > H - myb - W * 0.04) return false;
34
36
  for (i = 0; i < R.length; i++) { var r = R[i]; if (x0 < r.x1 && x1 > r.x0 && y0 < r.y1 && y1 > r.y0) return false; }
35
37
  for (i = 0; i < colocados.length; i++) { var q = colocados[i]; if (x0 < q[2] && x1 > q[0] && y0 < q[3] && y1 > q[1]) return false; }
@@ -48,13 +50,23 @@ function kAmbiente(comp, P, cam) {
48
50
  }
49
51
  }
50
52
  }
53
+ // onde o raio que sai de (cx, cy) no ângulo a cruza a borda da caixa (+ m para fora); sem caixa, o próprio centro
54
+ function kNaBorda(c, cx, cy, a, m) {
55
+ if (!c) return [cx, cy];
56
+ var dx = Math.cos(a), dy = Math.sin(a), hw = (c[2] - c[0]) / 2, hh = (c[3] - c[1]) / 2;
57
+ var k = Math.min(Math.abs(dx) > 1e-6 ? hw / Math.abs(dx) : 1e9, Math.abs(dy) > 1e-6 ? hh / Math.abs(dy) : 1e9) + (m || 0);
58
+ return [cx + dx * k, cy + dy * k];
59
+ }
51
60
  // pontos que estalam em volta de um centro (confete discreto), com som
52
- function kEstouro(comp, cam, cx, cy, t, cor, n, raio, som0) {
61
+ /* caixa (opcional, [x0, y0, x1, y1]): o estouro nasce na BORDA dela e vai para fora — o confete nunca passa por cima do texto que
62
+ mora dentro (o rótulo do botão virava "Começa: agora" com um ponto branco em cima do "r"). */
63
+ function kEstouro(comp, cam, cx, cy, t, cor, n, raio, som0, caixa) {
53
64
  var i;
54
65
  for (i = 0; i < n; i++) {
55
66
  var a = (i / n) * Math.PI * 2 + 0.4, r = raio * (0.8 + 0.4 * ((i * 37) % 10) / 10), d = 10 + (i % 3) * 6;
56
- var L = forma(comp, 'circulo', { x: cx, y: cy, w: d * 2, cor: cor, parent: cam.no, nome: 'estouro', alfa: 0 });
57
- curva(kP(L), [[t, [cx, cy], null, 20], [t + 0.7, [cx + Math.cos(a) * r, cy + Math.sin(a) * r], 85, null]]);
67
+ var p0 = kNaBorda(caixa, cx, cy, a, d + 6), p1 = [p0[0] + Math.cos(a) * r, p0[1] + Math.sin(a) * r];
68
+ var L = forma(comp, 'circulo', { x: p0[0], y: p0[1], w: d * 2, cor: cor, parent: cam.no, nome: 'estouro', alfa: 0 });
69
+ curva(kP(L), [[t, p0, null, 20], [t + 0.7, p1, 85, null]]);
58
70
  curva(kO(L), [[t - 0.04, 0, null, 30], [t, 100, null, 30], [t + 0.2, 100, 50, 30], [t + 0.75, 0, 70, null]]);
59
71
  curva(kS(L), [[t, [40, 40], null, 20], [t + 0.25, [100, 100], 70, 30], [t + 0.75, [20, 20], 70, null]]);
60
72
  }
@@ -203,8 +215,8 @@ function cenaAntesDepois(comp, P, d) {
203
215
  var seta = texto(comp, '>', { fonte: P.fonteTitulo, tamanho: 190 * u, cor: P.ac, x: v ? W / 2 : W / 2, y: v ? H * 0.5 : H * 0.5, parent: cam.no, entrada: 'palavra', t0: t2 - 0.25, sublinhado: false, largura: 200, sobrepoe: true, semAuditoria: true });
204
216
  if (v) for (var s = 0; s < seta.camadas.length; s++) kR(seta.camadas[s]).expression = 'value + 90';
205
217
  som(t2 - 0.25, 'swoosh', 0.6);
206
- var B = forma(comp, 'retangulo', { x: bx, y: by, w: cw, h: ch, r: 56 * u, cor: P.ac, parent: cam.no, nome: 'depois' });
207
- var cor2 = kLum(P.ac) > 0.5 ? '#14110F' : '#FFFFFF';
218
+ var B = forma(comp, 'retangulo', { x: bx, y: by, w: cw, h: ch, r: 56 * u, cor: kCardAc(P), parent: cam.no, nome: 'depois' });
219
+ var cor2 = kSobreCard(P);
208
220
  mola(kS(B), t2, [60, 60], [100, 100], { quique: 0.7, dur: 0.75 });
209
221
  curva(kO(B), [[t2, 0, null, 30], [t2 + 0.1, 100, 60, null]]);
210
222
  mola(kR(B), t2, v ? 3 : -3, 0, { quique: 0.5, dur: 0.8 });
@@ -212,7 +224,7 @@ function cenaAntesDepois(comp, P, d) {
212
224
  texto(comp, 'DEPOIS', { fonte: P.fonteTitulo, tamanho: 44 * u, cor: P.bg, x: bx, y: by - ch / 2, parent: cam.no, entrada: 'sobe', t0: t2 + 0.18, largura: 220 * u, maxLinhas: 1, sublinhado: false });
213
225
  texto(comp, d.depois || '', { tamanho: 96 * u, cor: cor2, corDestaque: cor2, x: bx, y: by + 24 * u, parent: cam.no, entrada: 'palavra', t0: t2 + 0.32, largura: cw * 0.84, maxLinhas: 4, sublinhado: false });
214
226
  som(t2, 'impacto', 0.7); som(t2 + 0.05, 'brilho', 0.6);
215
- kEstouro(comp, cam, bx, by, t2 + 0.05, P.fg, 8, ch * 0.75, false);
227
+ kEstouro(comp, cam, bx, by, t2 + 0.05, P.fg, 8, ch * 0.4, false, [bx - cw / 2, by - ch / 2, bx + cw / 2, by + ch / 2]);
216
228
  cam.empurra(0.8, v ? 0 : -60 * u, v ? -120 * u : 0, 1.02).empurra(t2 + 0.5, v ? 0 : 70 * u, v ? 130 * u : 0, 1.1).empurra(comp.duration - 0.2, v ? 0 : 90 * u, v ? 150 * u : 0, 1.13);
217
229
  fecha(comp);
218
230
  }
@@ -263,6 +275,19 @@ function cenaCitacao(comp, P, d) {
263
275
  fecha(comp);
264
276
  }
265
277
 
278
+ // ---------------------------------------------------------------- o cursor que clica num botão
279
+ /* kCursorNoBotao(comp, alvo, rot, ox, oy, cresce, preferido, u): a ponta do clique (no espaço do pai do cursor) pela regra da guarda
280
+ (gCursor): dentro do botão `alvo`, a setinha sem cobrir o rótulo `rot` (o retorno do texto(), com x, y, w, h no espaço do grupo do
281
+ botão, que está em ox, oy e cresce até `cresce`× no clique). Escreve a linha GUARDA da cena com a caixa do cursor e a do rótulo. */
282
+ function kCursorNoBotao(comp, alvo, rot, ox, oy, cresce, preferido, u) {
283
+ var k = cresce || 1, cx = ox || 0, cy = oy || 0, rx = rot && rot.w ? rot.x : 0, ry = rot && rot.w ? rot.y : 0;
284
+ var rc0 = rot && rot.w ? [cx + (rx - rot.w / 2) * k, cy + (ry - rot.h / 2) * k, cx + (rx + rot.w / 2) * k, cy + (ry + rot.h / 2) * k] : null;
285
+ var r = gCursor(alvo, rc0 ? [rc0] : [], { w: 48 * u, h: 70 * u, folga: 10 * u, preferido: preferido });
286
+ var log = ['cursor: clica em [' + Math.round(r.ponta[0]) + ',' + Math.round(r.ponta[1]) + ']' + (r.movido ? ' (saiu de cima do texto)' : '') + (r.ok ? '' : ' na borda de baixo do botão')];
287
+ writeLn('GUARDA ' + JSON.stringify({ cena: KIT.cenaIdx, w: comp.width, h: comp.height, log: log, caixas: [{ tipo: 'cursor', nome: 'cursor', c: [Math.round(r.c[0]), Math.round(r.c[1]), Math.round(r.c[2]), Math.round(r.c[3])], fora: 0 }].concat(rc0 ? [{ tipo: 'texto', nome: 'rótulo do botão', c: [Math.round(rc0[0]), Math.round(rc0[1]), Math.round(rc0[2]), Math.round(rc0[3])], fora: 0 }] : []), violacoes: gVerificar([{ tipo: 'cursor', nome: 'cursor', c: r.c }].concat(rc0 ? [{ tipo: 'texto', nome: 'rótulo', c: rc0, livre: false }] : []), comp.width, comp.height, { zona: { x0: -1e9, x1: 1e9, y0: -1e9, y1: 1e9, trilho: null } }) }));
288
+ return r.ponta;
289
+ }
290
+
266
291
  // ---------------------------------------------------------------- CTA
267
292
  /* O CTA: título que voa, botão SÓLIDO com brilho que varre (Merge Paths em Intersect: o brilho só existe dentro da pílula) e um
268
293
  ícone; o cursor entra, o botão afunda e volta com quique, estoura em ondas e partículas, com clique + impacto. Sem camada fantasma. */
@@ -296,7 +321,7 @@ function cenaCta(comp, P, d) {
296
321
  var ic = caminho(comp, [[-22 * u, 0], [24 * u, 0]], { cor: ink, esp: 11 * u, parent: btn, nome: 'seta-haste' });
297
322
  caminho(comp, [[6 * u, -20 * u], [26 * u, 0], [6 * u, 20 * u]], { cor: ink, esp: 11 * u, parent: btn, nome: 'seta-ponta' });
298
323
  var arrows = [];
299
- texto(comp, d.botao || 'Começar agora', { fonte: P.fonteTitulo, tamanho: (v ? 84 : 58) * u, cor: ink, corDestaque: ink, x: -28 * u, y: 0, parent: btn, entrada: 'sobe', t0: tb + 0.08, largura: bw * 0.7, maxLinhas: 1, sublinhado: false, livre: true, fala: false });
324
+ var rot = texto(comp, d.botao || 'Começar agora', { fonte: P.fonteTitulo, tamanho: (v ? 84 : 58) * u, cor: ink, corDestaque: ink, x: -28 * u, y: 0, parent: btn, entrada: 'sobe', t0: tb + 0.08, largura: bw * 0.7, maxLinhas: 1, sublinhado: false, livre: true, fala: false });
300
325
  var iconX = bw / 2 - 74 * u;
301
326
  var j, kids = [];
302
327
  // posiciona a seta à direita do rótulo
@@ -307,7 +332,10 @@ function cenaCta(comp, P, d) {
307
332
  // o cursor entra, o botão afunda e explode
308
333
  var tp = tb + 1.05;
309
334
  var cur = kCursor(comp, cam.no, u, '#FFFFFF', '#101010');
310
- var c0 = [bx + bw * 0.5, by + 460 * u], c1 = [bx + bw * 0.4, by + 6 * u]; // clica na borda direita, no ícone da seta (nunca em cima do texto)
335
+ // o cursor é ELEMENTO para a guarda: a ponta clica dentro do botão, depois do texto (na seta) ou na borda; a setinha
336
+ // inteira (48×70 a partir da ponta) nunca cobre o rótulo, nem com o botão crescendo 10% no estouro do clique
337
+ var c1 = kCursorNoBotao(comp, [bx - bw / 2, by - bh / 2, bx + bw / 2, by + bh / 2], rot, bx, by, 1.1, [bx + iconX - 4 * u, by + 10 * u], u);
338
+ var c0 = [c1[0] + bw * 0.1, by + 460 * u];
311
339
  kP(cur).setValue(c0);
312
340
  curva(kP(cur), [[tp - 0.75, c0, null, 18], [tp - 0.05, c1, 82, 40]]);
313
341
  curva(kO(cur), [[tp - 0.75, 0, null, 30], [tp - 0.55, 100, 60, null], [tp + 0.8, 100, null, 30], [tp + 1.05, 0, 60, null]]);
@@ -319,8 +347,9 @@ function cenaCta(comp, P, d) {
319
347
  curva(kS(on), [[tp + j * 0.07, [100, 100], null, 20], [tp + j * 0.07 + 0.7, [150 + j * 14, 220 + j * 30], 80, null]]);
320
348
  curva(kO(on), [[tp + j * 0.07 - 0.02, 0, null, 30], [tp + j * 0.07, 85, null, 30], [tp + j * 0.07 + 0.7, 0, 70, null]]);
321
349
  }
322
- kEstouro(comp, cam, bx, by, tp + 0.04, P.ac, 16, bw * 0.4, false);
323
- kEstouro(comp, cam, bx, by, tp + 0.08, P.fg, 10, bw * 0.3, false);
350
+ var cxBotao = [bx - bw / 2 * 1.1, by - bh / 2 * 1.1, bx + bw / 2 * 1.1, by + bh / 2 * 1.1]; // o botão a 110% no estouro
351
+ kEstouro(comp, cam, bx, by, tp + 0.04, P.ac, 16, bw * 0.25, false, cxBotao);
352
+ kEstouro(comp, cam, bx, by, tp + 0.08, P.fg, 10, bw * 0.18, false, cxBotao);
324
353
  som(tp, 'clique', 0.9); som(tp + 0.03, 'impacto', 0.6); som(tp + 0.1, 'brilho', 0.6);
325
354
  // depois do clique o botão respira e solta um halo, sem parar
326
355
  var halo = forma(comp, 'pilula', { x: bx, y: by, w: bw, h: bh, traco: P.ac, tracoL: 6 * u, alfa: 0, parent: cam.no, nome: 'halo' });
@@ -331,14 +360,14 @@ function cenaCta(comp, P, d) {
331
360
  }
332
361
 
333
362
  // ---------------------------------------------------------------- montagem
334
- /* O vocabulário de cenas: 8 clássicas (aqui) + 30 primitivas (primitivas.jsx). O diretor (maquina-diretor.js) escolhe. */
363
+ /* O vocabulário de cenas: 8 clássicas (aqui) + 31 primitivas (primitivas.jsx; a 31ª, "foto", só entra quando o pedido traz foto). O diretor (maquina-diretor.js) escolhe. */
335
364
  var CENAS = {
336
365
  gancho: cenaGancho, celular: cenaCelular, numero: cenaNumero, lista: cenaLista, antes_depois: cenaAntesDepois, icones: cenaIcones, citacao: cenaCitacao, cta: cenaCta,
337
366
  cinetico: cenaCinetico, interface: cenaInterface, mergulho: cenaMergulho, camera_nulls: cenaCameraNulls, barras: cenaBarras, linha: cenaLinha, mapa: cenaMapa,
338
367
  cards_pulam: cenaCardsPulam, cursor: cenaCursor, notificacoes: cenaNotificacoes, chat: cenaChat, linha_tempo: cenaLinhaTempo, laco: cenaLaco, explosao: cenaExplosao,
339
368
  revela: cenaRevela, split: cenaSplit, heroi: cenaHeroi, particulas: cenaParticulas, relogio: cenaRelogio, calendario: cenaCalendario, funil: cenaFunil,
340
369
  parede_logos: cenaParedeLogos, prompt: cenaPrompt, odometro: cenaOdometro, medidor: cenaMedidor, carimbo: cenaCarimbo, leque: cenaLeque, ondas: cenaOndas,
341
- rolagem: cenaRolagem, estrelas: cenaEstrelas
370
+ rolagem: cenaRolagem, estrelas: cenaEstrelas, foto: cenaFoto
342
371
  };
343
372
 
344
373
  function montar(PL) {
@@ -366,7 +395,10 @@ function montar(PL) {
366
395
  var c = PL.cenas[i], dur = c.dur + (i < n - 1 ? TROCA_DUR + 0.05 : 0), f = Math.max(1, Math.min(2.4, c.f || 1));
367
396
  // a cena é montada em tempo NOMINAL (dur × f) e tocada f vezes mais rápido dentro de uma comp-embrulho: tudo entra nos primeiros ~35%
368
397
  var comp = novaComp('CENA ' + i, W, H, dur * f, fps);
369
- KIT.cenaComElemento = !!c.elemento; KIT.semAmbiente = !!c.semAmbiente || !{ cinetico: 1, gancho: 1, numero: 1, citacao: 1, particulas: 1, odometro: 1, relogio: 1, carimbo: 1 }[c.tipo]; // enfeite só onde há espaço livre de verdade (nada de herói em cima de janela, prompt ou botão) KIT.semRotulos = !!c.semRotulos; KIT.cenaHeroi = c.tipo == 'heroi' || c.tipo == 'revela'; KIT.titulosL = []; KIT.tituloB = null; KIT.t0 = starts[i]; KIT.cenaIdx = i; KIT.estilo = c.camera || PL.camera || 'deriva'; KIT.ajusteCena = c.ajuste || 1; KIT.f = f; KIT.entMax = 0;
398
+ // enfeite só onde há espaço livre de verdade (nada de herói em cima de janela, prompt ou botão)
399
+ KIT.cenaComElemento = !!c.elemento; KIT.semAmbiente = !!c.semAmbiente || !{ cinetico: 1, gancho: 1, numero: 1, citacao: 1, particulas: 1, odometro: 1, relogio: 1, carimbo: 1 }[c.tipo];
400
+ // (até 20/10 esta linha morava DEPOIS do comentário acima, na mesma linha: o cenaIdx, o t0, o ajuste e o f nunca eram postos)
401
+ KIT.semRotulos = !!c.semRotulos; KIT.cenaHeroi = c.tipo == 'heroi' || c.tipo == 'revela'; KIT.titulosL = []; KIT.tituloB = null; KIT.t0 = starts[i]; KIT.cenaIdx = i; KIT.estilo = c.camera || PL.camera || 'deriva'; KIT.ajusteCena = c.ajuste || 1; KIT.tituloDesce = c.tituloDesce || 0; KIT.f = f; KIT.entMax = 0;
370
402
  var P = c.pal; KIT.pal = P;
371
403
  P.fonteTitulo = PL.fontes.titulo; P.fonteTexto = PL.fontes.texto; P.fonteDestaque = PL.fontes.destaque;
372
404
  if (!CENAS[c.tipo]) KIT.avisos.push('cena ' + i + ': tipo desconhecido ' + c.tipo);
@@ -0,0 +1,274 @@
1
+ /* guarda.jsx — a GUARDA de layout do motion (ES3: só var e function).
2
+
3
+ O mesmo arquivo roda em dois lugares, ANTES do render:
4
+ 1. no Node (lib/estudio/maquina-guarda.js carrega pelo vm): planeja a cena "foto" (foto, título, ícones) com a largura
5
+ medida da fonte e escreve o layout no plano;
6
+ 2. no kit (o maquina.js concatena depois do base.jsx): no fecha() de CADA cena, com as caixas reais (sourceRectAtTime),
7
+ conserta o que o modelo ou o diretor puseram torto.
8
+
9
+ Regras (a régua do dono: muito movimento, mas NADA feio — nada gigante, nada sobreposto, nada colado na borda):
10
+ tamanho texto até 88% da largura; ícone entre 12% e 24% do lado menor; foto entre 55% e 80% da largura no 9:16
11
+ zona tudo dentro de 6% de margem; no 9:16 fora das faixas de interface do Reels/TikTok (10% em cima, 20% embaixo)
12
+ e do trilho de botões da direita
13
+ sobrepor texto não encosta em nada (folga mínima); ícone nunca sobre texto, foto/rosto ou outro ícone
14
+ lotação no máximo N elementos ao mesmo tempo (os ícones de menor prioridade saem)
15
+ Quando viola, CONSERTA (reposiciona, encolhe, tira o ícone de trás); se não der, pede a troca da peça (trocar: true).
16
+ Cada conserto vira uma linha do log da cena.
17
+
18
+ item = { id, tipo: 'texto'|'icone'|'foto'|'cartao'|'cursor', c: [x0, y0, x1, y1] (o envelope de todos os quadros-chave),
19
+ fixo (não move nem encolhe), prio (maior fica), nome, alvo (cursor: a caixa do botão que ele clica) }
20
+ cursor a setinha é elemento: nunca em cima de letra; clica na borda do botão ou depois do texto (gCursor)
21
+ */
22
+
23
+ var GUARDA = { margem: 0.06, folga: 28, maxSimult: 6, textoMaxL: 0.88, iconeMin: 0.12, iconeMax: 0.24, fotoMin: 0.55, fotoMax: 0.8, passo: 0.025 };
24
+
25
+ function gAlto(W, H) { return H / W > 1.6; }
26
+ /* A zona segura (no mesmo espaço das caixas). o.legenda: a legenda sobe a faixa de baixo. */
27
+ function gZona(W, H, o) {
28
+ o = o || {};
29
+ var m = GUARDA.margem, z = { x0: W * m, x1: W * (1 - m), y0: H * m, y1: H * (1 - m), trilho: null };
30
+ if (gAlto(W, H)) {
31
+ z.y0 = H * 0.1; z.y1 = H * (o.legenda ? 0.73 : 0.8);
32
+ z.trilho = [W * 0.885, H * 0.5, W, H]; // a coluna de curtir/comentar/compartilhar
33
+ }
34
+ return z;
35
+ }
36
+ function gW(c) { return c[2] - c[0]; }
37
+ function gH(c) { return c[3] - c[1]; }
38
+ function gCx(c) { return (c[0] + c[2]) / 2; }
39
+ function gCy(c) { return (c[1] + c[3]) / 2; }
40
+ function gR(n) { return Math.round(n); }
41
+ function gTxt(c) { return '[' + gR(c[0]) + ',' + gR(c[1]) + ',' + gR(c[2]) + ',' + gR(c[3]) + ']'; }
42
+ function gCaixa(cx, cy, w, h) { return [cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2]; }
43
+ function gMove(c, dx, dy) { return [c[0] + dx, c[1] + dy, c[2] + dx, c[3] + dy]; }
44
+ function gEscala(c, k) { return gCaixa(gCx(c), gCy(c), gW(c) * k, gH(c) * k); }
45
+ /* Encostam? (com folga m entre as duas) */
46
+ function gToca(a, b, m) { m = m || 0; return a[0] < b[2] + m && b[0] < a[2] + m && a[1] < b[3] + m && b[1] < a[3] + m; }
47
+ function gCabe(c, z) { return c[0] >= z.x0 - 0.5 && c[2] <= z.x1 + 0.5 && c[1] >= z.y0 - 0.5 && c[3] <= z.y1 + 0.5; }
48
+ function gNoTrilho(c, z) { return !!z.trilho && gToca(c, z.trilho, 0); }
49
+ function gNome(it) { return it.nome || (it.tipo + ' ' + it.id); }
50
+
51
+ /* Põe a caixa dentro da zona (encolhe se for maior). Devolve a caixa nova. */
52
+ function gEncaixa(c, z) {
53
+ var w = gW(c), h = gH(c), zw = z.x1 - z.x0, zh = z.y1 - z.y0, k = Math.min(1, zw / w, zh / h);
54
+ if (k < 1) c = gEscala(c, k);
55
+ var dx = 0, dy = 0;
56
+ if (c[0] < z.x0) dx = z.x0 - c[0]; else if (c[2] > z.x1) dx = z.x1 - c[2];
57
+ if (c[1] < z.y0) dy = z.y0 - c[1]; else if (c[3] > z.y1) dy = z.y1 - c[3];
58
+ return gMove(c, dx, dy);
59
+ }
60
+
61
+ /* Procura um lugar livre do tamanho (w, h) o mais perto de (cx, cy): nada em `ocupado` (com folga), dentro da zona, fora do trilho. */
62
+ function gLugarLivre(cx, cy, w, h, z, ocupado, folga, W) {
63
+ var passo = Math.max(8, W * GUARDA.passo), melhor = null, md = 1e18, x, y, j;
64
+ for (y = z.y0 + h / 2; y <= z.y1 - h / 2 + 0.5; y += passo) {
65
+ for (x = z.x0 + w / 2; x <= z.x1 - w / 2 + 0.5; x += passo) {
66
+ var c = gCaixa(x, y, w, h), livre = !gNoTrilho(c, z);
67
+ for (j = 0; livre && j < ocupado.length; j++) if (gToca(c, ocupado[j], folga)) livre = false;
68
+ if (!livre) continue;
69
+ var d = (x - cx) * (x - cx) + (y - cy) * (y - cy);
70
+ if (d < md) { md = d; melhor = c; }
71
+ }
72
+ }
73
+ return melhor;
74
+ }
75
+
76
+ /* O CURSOR é um elemento como os outros: a ponta clica DENTRO do alvo (botão, campo), mas a setinha inteira (da ponta para a
77
+ direita e para baixo) nunca cobre letra. Clica na borda do botão ou depois do texto.
78
+ gCursor(alvo, textos, o) → { ponta: [x, y], c: caixa da setinha, movido, ok }
79
+ o = { w, h (a setinha a partir da ponta), folga, preferido: [x, y], pilula (o alvo é pílula: a ponta fica dentro do arredondado) } */
80
+ function gCaixaCursor(p, w, h) { return [p[0] - 3, p[1] - 3, p[0] + w, p[1] + h]; }
81
+ function gDentroAlvo(p, c, pilula) {
82
+ var h = gH(c), r = h / 2, cy = gCy(c), m = h * 0.1;
83
+ if (!pilula) return p[0] >= c[0] + m && p[0] <= c[2] - m && p[1] >= c[1] + m && p[1] <= c[3] - m;
84
+ var x = Math.max(c[0] + r, Math.min(c[2] - r, p[0]));
85
+ return (p[0] - x) * (p[0] - x) + (p[1] - cy) * (p[1] - cy) <= (r - m) * (r - m);
86
+ }
87
+ function gCursor(alvo, textos, o) {
88
+ o = o || {};
89
+ var w = o.w || 48, h = o.h || 70, folga = o.folga != null ? o.folga : 6, i, j, x, y;
90
+ var pref = o.preferido || [alvo[2] - gH(alvo) * 0.45, gCy(alvo) + gH(alvo) * 0.12];
91
+ var livre = function (p) {
92
+ if (!gDentroAlvo(p, alvo, o.pilula !== false)) return false;
93
+ var c = gCaixaCursor(p, w, h);
94
+ for (j = 0; j < textos.length; j++) if (gToca(c, textos[j], folga)) return false;
95
+ return true;
96
+ };
97
+ if (livre(pref)) return { ponta: pref, c: gCaixaCursor(pref, w, h), movido: false, ok: true };
98
+ var passo = Math.max(4, Math.min(gW(alvo), gH(alvo)) / 12), melhor = null, md = 1e18;
99
+ for (y = alvo[1]; y <= alvo[3]; y += passo) {
100
+ for (x = alvo[0]; x <= alvo[2]; x += passo) {
101
+ if (!livre([x, y])) continue;
102
+ var d = (x - pref[0]) * (x - pref[0]) + (y - pref[1]) * (y - pref[1]);
103
+ if (d < md) { md = d; melhor = [x, y]; }
104
+ }
105
+ }
106
+ if (melhor) return { ponta: melhor, c: gCaixaCursor(melhor, w, h), movido: true, ok: true };
107
+ // sem lugar dentro sem cobrir letra: a ponta vai para a borda de baixo do botão, abaixo do texto (a setinha pende para fora)
108
+ var baixo = alvo[3] - gH(alvo) * 0.06;
109
+ for (j = 0; j < textos.length; j++) if (gToca(textos[j], alvo, 0) && textos[j][3] + folga + 3 > baixo) baixo = textos[j][3] + folga + 3;
110
+ var p2 = [alvo[2] - gH(alvo) * 0.5, baixo];
111
+ return { ponta: p2, c: gCaixaCursor(p2, w, h), movido: true, ok: false };
112
+ }
113
+
114
+ /* TEXTO DENTRO DE CARTÃO (faixa, placa, botão): o texto cabe no cartão com folga. Não cabe: o corpo encolhe até o mínimo (kMin);
115
+ ainda não cabe: o cartão cresce em volta do texto (até a caixa `max`, se houver).
116
+ gTextoNoCartao(t, c, o) → { k (escala do texto), cartao (a caixa nova), cresceu, ok, log } o = { folga, kMin, max } */
117
+ function gTextoNoCartao(t, c, o) {
118
+ o = o || {};
119
+ var f = o.folga || 0, kMin = o.kMin == null ? 0.8 : o.kMin, log = [];
120
+ var k = Math.min(1, (gW(c) - 2 * f) / Math.max(1, gW(t)), (gH(c) - 2 * f) / Math.max(1, gH(t)));
121
+ var dentro = t[0] >= c[0] + f - 0.5 && t[2] <= c[2] - f + 0.5 && t[1] >= c[1] + f - 0.5 && t[3] <= c[3] - f + 0.5;
122
+ if (k >= 1 && dentro) return { k: 1, cartao: c, cresceu: false, ok: true, log: log };
123
+ if (k >= 1) k = 1;
124
+ else if (k >= kMin) log.push('texto apertado no cartão: o corpo encolheu para ' + Math.round(k * 100) + '%');
125
+ else k = kMin;
126
+ var w = Math.max(gW(c), gW(t) * k + 2 * f), h = Math.max(gH(c), gH(t) * k + 2 * f), ok = true;
127
+ if (o.max) { if (w > gW(o.max)) { w = gW(o.max); ok = false; } if (h > gH(o.max)) { h = gH(o.max); ok = false; } }
128
+ var nc = gCaixa(gCx(t), gCy(t), w, h), cresceu = w > gW(c) + 0.5 || h > gH(c) + 0.5 || Math.abs(gCy(t) - gCy(c)) > 0.5 || Math.abs(gCx(t) - gCx(c)) > 0.5;
129
+ if (cresceu) log.push('texto vazava do cartão: o cartão foi para ' + Math.round(w) + '×' + Math.round(h) + (ok ? '' : ' (no máximo; o texto ainda encosta)'));
130
+ return { k: k, cartao: nc, cresceu: cresceu, ok: ok, log: log };
131
+ }
132
+
133
+ /*
134
+ * gConsertar(itens, W, H, o) → { itens, log: [texto], trocar: bool, violacoes: [texto] }
135
+ * o = { zona (gZona), folga, so: 'icones' (só move/encolhe/tira ícones e a foto: o kit, com o texto já posto), legenda,
136
+ * escalaMundo (layout → tela: a regra da foto 55–80% vale pro que se vê) }
137
+ * Cada item ganha: c (a caixa final), k (escala aplicada), dx/dy (deslocamento do centro), removido.
138
+ */
139
+ function gConsertar(itens, W, H, o) {
140
+ o = o || {};
141
+ var z = o.zona || gZona(W, H, o), folga = o.folga != null ? o.folga : GUARDA.folga * Math.min(W, H) / 1080;
142
+ var log = [], trocar = false, i, j, a, b, alto = gAlto(W, H), lado = Math.min(W, H), soIcones = o.so == 'icones', em = o.escalaMundo || 1;
143
+ var L = [];
144
+ for (i = 0; i < itens.length; i++) { a = itens[i]; a.c0 = [a.c[0], a.c[1], a.c[2], a.c[3]]; a.removido = false; L.push(a); }
145
+ var pode = function (it) { return !it.fixo && (!soIcones || it.tipo == 'icone' || it.movel); };
146
+
147
+ // 1. tamanho por tipo
148
+ for (i = 0; i < L.length; i++) {
149
+ a = L[i];
150
+ if (!pode(a)) continue;
151
+ if (a.tipo == 'texto' && gW(a.c) > W * GUARDA.textoMaxL) { a.c = gEscala(a.c, W * GUARDA.textoMaxL / gW(a.c)); log.push('texto largo: ' + gNome(a) + ' encolheu para ' + Math.round(GUARDA.textoMaxL * 100) + '% da largura'); }
152
+ if (a.tipo == 'icone') {
153
+ var s = Math.max(gW(a.c), gH(a.c)) / (a.infla || 1);
154
+ if (s > lado * GUARDA.iconeMax) { a.c = gEscala(a.c, lado * GUARDA.iconeMax / s); log.push('ícone gigante: ' + gNome(a) + ' de ' + Math.round(s) + ' px para ' + Math.round(lado * GUARDA.iconeMax) + ' px'); }
155
+ else if (s < lado * GUARDA.iconeMin) { a.c = gEscala(a.c, lado * GUARDA.iconeMin / s); log.push('ícone pequeno: ' + gNome(a) + ' cresceu para ' + Math.round(lado * GUARDA.iconeMin) + ' px'); }
156
+ }
157
+ if (a.tipo == 'foto' && alto) {
158
+ var fw = gW(a.c) / (a.infla || 1) * em; // no MUNDO: a regra é sobre o que o espectador vê
159
+ if (fw < W * GUARDA.fotoMin - 0.5) { a.c = gEscala(a.c, W * GUARDA.fotoMin / fw); log.push('foto pequena: ' + Math.round(fw / W * 100) + '% da largura, cresceu para ' + Math.round(GUARDA.fotoMin * 100) + '%'); }
160
+ else if (fw > W * GUARDA.fotoMax + 0.5) { a.c = gEscala(a.c, W * GUARDA.fotoMax / fw); log.push('foto gigante: ' + Math.round(fw / W * 100) + '% da largura, encolheu para ' + Math.round(GUARDA.fotoMax * 100) + '%'); }
161
+ }
162
+ }
163
+ // 2. zona segura (e o trilho de botões do 9:16)
164
+ for (i = 0; i < L.length; i++) {
165
+ a = L[i];
166
+ if (!pode(a) || a.livre) continue;
167
+ var antes = a.c;
168
+ if (!gCabe(a.c, z)) a.c = gEncaixa(a.c, z);
169
+ if (gNoTrilho(a.c, z) && (a.tipo == 'icone' || a.tipo == 'foto')) {
170
+ var nx = gMove(a.c, z.trilho[0] - folga * 0.5 - a.c[2], 0);
171
+ if (gCabe(nx, z)) a.c = nx; else { var ny = gMove(a.c, 0, z.trilho[1] - a.c[3]); if (gCabe(ny, z)) a.c = ny; }
172
+ }
173
+ if (antes !== a.c) log.push('fora da zona segura: ' + gNome(a) + ' ' + gTxt(antes) + ' → ' + gTxt(a.c));
174
+ }
175
+ // 3. texto × foto/cartão: afasta (a foto sobe ou desce; ou o texto), depois encolhe a foto até o mínimo; senão, troca a peça
176
+ for (i = 0; i < L.length; i++) {
177
+ a = L[i];
178
+ if (a.tipo != 'texto') continue;
179
+ for (j = 0; j < L.length; j++) {
180
+ b = L[j];
181
+ if (b === a || (b.tipo != 'foto' && b.tipo != 'cartao') || !gToca(a.c, b.c, folga)) continue;
182
+ var feito = false, tentativa;
183
+ if (pode(b)) { // a foto vai para o lado oposto ao do texto
184
+ var cima = gCy(b.c) <= gCy(a.c);
185
+ for (tentativa = 0; tentativa < 12 && !feito; tentativa++) {
186
+ var nb = cima ? gMove(b.c, 0, a.c[1] - folga - b.c[3]) : gMove(b.c, 0, a.c[3] + folga - b.c[1]);
187
+ if (gCabe(nb, z)) { log.push('texto encostado na foto: ' + gNome(b) + ' foi para ' + (cima ? 'cima' : 'baixo') + (tentativa ? ' (encolhida ' + Math.round(gW(b.c) / W * 100) + '%)' : '')); b.c = nb; feito = true; break; }
188
+ var min = b.tipo == 'foto' && alto ? W * GUARDA.fotoMin * (b.infla || 1) / em : gW(b.c0) * 0.6;
189
+ if (gW(b.c) * 0.94 < min) break;
190
+ b.c = gEscala(b.c, 0.94);
191
+ }
192
+ }
193
+ if (!feito && pode(a)) {
194
+ var embaixo = gCy(a.c) >= gCy(b.c);
195
+ var na = embaixo ? gMove(a.c, 0, b.c[3] + folga - a.c[1]) : gMove(a.c, 0, b.c[1] - folga - a.c[3]);
196
+ if (gCabe(na, z)) { log.push('texto encostado em ' + gNome(b) + ': o texto foi para ' + (embaixo ? 'baixo' : 'cima')); a.c = na; feito = true; }
197
+ }
198
+ if (!feito) { trocar = true; log.push('não coube: ' + gNome(a) + ' e ' + gNome(b) + ' se sobrepõem sem saída: troca a peça'); }
199
+ }
200
+ }
201
+ // 3b. cursor: nunca em cima de letra; a ponta continua dentro do alvo (o botão), a setinha vai para a borda ou depois do texto
202
+ var textosC = [];
203
+ for (i = 0; i < L.length; i++) if (L[i].tipo == 'texto' && !L[i].removido) textosC.push(L[i].c);
204
+ for (i = 0; i < L.length; i++) {
205
+ a = L[i];
206
+ if (a.tipo != 'cursor' || !a.alvo) continue;
207
+ var bateC = false;
208
+ for (j = 0; j < textosC.length; j++) if (gToca(a.c, textosC[j], folga * 0.25)) bateC = true;
209
+ if (!bateC) continue;
210
+ var rc = gCursor(a.alvo, textosC, { w: gW(a.c) - 3, h: gH(a.c) - 3, folga: folga * 0.25, pilula: a.pilula !== false });
211
+ log.push('cursor sobre o texto: ' + gNome(a) + ' ' + gTxt(a.c) + ' → ' + gTxt(rc.c) + (rc.ok ? '' : ' (na borda do botão)'));
212
+ a.c = rc.c; a.ponta = rc.ponta;
213
+ }
214
+ // 4. ícones: nunca sobre texto, foto/rosto, cartão ou outro ícone; acha um lugar livre, encolhe até o mínimo, ou sai
215
+ var ordem = [];
216
+ for (i = 0; i < L.length; i++) if (L[i].tipo == 'icone') ordem.push(L[i]);
217
+ ordem.sort(function (p, q) { return (q.prio || 0) - (p.prio || 0); });
218
+ var ocupado = [];
219
+ for (i = 0; i < L.length; i++) if (L[i].tipo != 'icone') ocupado.push(L[i].c);
220
+ for (i = 0; i < ordem.length; i++) {
221
+ a = ordem[i];
222
+ var bate = !gCabe(a.c, z) || gNoTrilho(a.c, z), quem = '';
223
+ for (j = 0; !bate && j < ocupado.length; j++) if (gToca(a.c, ocupado[j], folga)) bate = true;
224
+ if (!bate) { ocupado.push(a.c); continue; }
225
+ if (!pode(a)) { log.push('ícone fixo sobreposto: ' + gNome(a)); continue; }
226
+ for (j = 0; j < L.length; j++) if (L[j] !== a && !L[j].removido && L[j].tipo != 'icone' && gToca(a.c, L[j].c, folga)) { quem = L[j].tipo == 'texto' ? 'o texto' : (L[j].tipo == 'foto' ? 'a foto' : gNome(L[j])); break; }
227
+ var w = gW(a.c), h = gH(a.c), achou = null, k = 1, s0 = Math.max(w, h) / (a.infla || 1), minK = Math.min(1, lado * GUARDA.iconeMin / s0);
228
+ while (!achou && k >= minK - 0.001) {
229
+ achou = gLugarLivre(gCx(a.c), gCy(a.c), w * k, h * k, z, ocupado, folga, W);
230
+ if (!achou) k = k > minK + 0.001 ? Math.max(minK, k * 0.85) : minK - 1;
231
+ }
232
+ if (achou) {
233
+ log.push('ícone ' + (quem ? 'sobre ' + quem : 'fora do lugar') + ': ' + gNome(a) + ' ' + gTxt(a.c) + ' → ' + gTxt(achou) + (k < 0.999 ? ' (encolhido ' + Math.round(k * 100) + '%)' : ''));
234
+ a.c = achou; ocupado.push(achou);
235
+ } else { a.removido = true; log.push('ícone sem lugar livre' + (quem ? ' (estava sobre ' + quem + ')' : '') + ': ' + gNome(a) + ' saiu'); }
236
+ }
237
+ // 5. lotação: no máximo N ao mesmo tempo (sai o ícone de menor prioridade)
238
+ var vivos = 0;
239
+ for (i = 0; i < L.length; i++) if (!L[i].removido) vivos++;
240
+ for (i = ordem.length - 1; i >= 0 && vivos > (o.maxSimult || GUARDA.maxSimult); i--) {
241
+ if (ordem[i].removido || !pode(ordem[i])) continue;
242
+ ordem[i].removido = true; vivos--; log.push('elementos demais na cena: ' + gNome(ordem[i]) + ' saiu');
243
+ }
244
+ for (i = 0; i < L.length; i++) {
245
+ a = L[i];
246
+ a.k = gW(a.c) / Math.max(1e-6, gW(a.c0));
247
+ a.dx = gCx(a.c) - gCx(a.c0); a.dy = gCy(a.c) - gCy(a.c0);
248
+ }
249
+ return { itens: L, log: log, trocar: trocar, violacoes: gVerificar(L, W, H, { zona: z, folga: folga * 0.5 }) };
250
+ }
251
+
252
+ /* Só confere (não mexe): o que ainda viola. É a conferência por caixa quando não há olho (Vision) depois do render. */
253
+ function gVerificar(itens, W, H, o) {
254
+ o = o || {};
255
+ var z = o.zona || gZona(W, H, o), folga = o.folga != null ? o.folga : 0, v = [], i, j, a, b;
256
+ for (i = 0; i < itens.length; i++) {
257
+ a = itens[i];
258
+ if (a.removido || a.livre) continue;
259
+ if (!gCabe(a.c, { x0: z.x0 - 6, x1: z.x1 + 6, y0: z.y0 - 6, y1: z.y1 + 6 })) v.push('fora da zona segura: ' + gNome(a) + ' ' + gTxt(a.c));
260
+ else if ((a.tipo == 'icone' || a.tipo == 'foto') && gNoTrilho(a.c, z)) v.push('no trilho de botões: ' + gNome(a));
261
+ for (j = i + 1; j < itens.length; j++) {
262
+ b = itens[j];
263
+ if (b.removido || b.livre) continue;
264
+ if (a.tipo == 'texto' && b.tipo == 'texto') continue; // texto × texto: quem confere é o kit (tempos diferentes)
265
+ if (a.tipo == 'cursor' || b.tipo == 'cursor') { // o cursor só não pode cobrir letra
266
+ if ((a.tipo == 'texto' || b.tipo == 'texto') && gToca(a.c, b.c, 0)) v.push('cursor sobre o texto: ' + gNome(a.tipo == 'cursor' ? b : a));
267
+ continue;
268
+ }
269
+ if ((a.tipo == 'cartao' && b.tipo == 'cartao')) continue;
270
+ if (gToca(a.c, b.c, folga - 1e-6) && (a.tipo == 'texto' || b.tipo == 'texto' || a.tipo == 'icone' || b.tipo == 'icone')) v.push('sobreposição: ' + gNome(a) + ' × ' + gNome(b));
271
+ }
272
+ }
273
+ return v;
274
+ }