primocode 10.13.0 → 10.14.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 (196) hide show
  1. package/README.md +66 -12
  2. package/bin/primocode.js +148 -8
  3. package/lib/act.js +56 -3
  4. package/lib/catalogo.js +12 -1
  5. package/lib/config.js +1 -0
  6. package/lib/estudio/cota.js +157 -0
  7. package/lib/estudio/dados/projetos/clube-ad5.json +105 -0
  8. package/lib/estudio/dados/projetos/clube-ae-sem-memoria.json +107 -0
  9. package/lib/estudio/dados/projetos/clube-agent.json +120 -0
  10. package/lib/estudio/dados/projetos/clube-antes-e-agora.json +86 -0
  11. package/lib/estudio/dados/projetos/clube-apresentando-clube.json +110 -0
  12. package/lib/estudio/dados/projetos/clube-bluevisa.json +106 -0
  13. package/lib/estudio/dados/projetos/clube-caught-in-the-fire.json +127 -0
  14. package/lib/estudio/dados/projetos/clube-chat-cliente-satisfeito.json +98 -0
  15. package/lib/estudio/dados/projetos/clube-chatgpt-anuncios.json +120 -0
  16. package/lib/estudio/dados/projetos/clube-claude-dj.json +140 -0
  17. package/lib/estudio/dados/projetos/clube-claude-mcp.json +142 -0
  18. package/lib/estudio/dados/projetos/clube-cleanui.json +106 -0
  19. package/lib/estudio/dados/projetos/clube-clinica-take-unico.json +128 -0
  20. package/lib/estudio/dados/projetos/clube-clinica.json +104 -0
  21. package/lib/estudio/dados/projetos/clube-clube-opener.json +125 -0
  22. package/lib/estudio/dados/projetos/clube-clube.json +108 -0
  23. package/lib/estudio/dados/projetos/clube-codigo.json +101 -0
  24. package/lib/estudio/dados/projetos/clube-distilbook.json +104 -0
  25. package/lib/estudio/dados/projetos/clube-drive-upload.json +115 -0
  26. package/lib/estudio/dados/projetos/clube-dub.json +119 -0
  27. package/lib/estudio/dados/projetos/clube-enxovaly.json +105 -0
  28. package/lib/estudio/dados/projetos/clube-fecha7.json +118 -0
  29. package/lib/estudio/dados/projetos/clube-filatov.json +128 -0
  30. package/lib/estudio/dados/projetos/clube-frame.json +108 -0
  31. package/lib/estudio/dados/projetos/clube-glass.json +118 -0
  32. package/lib/estudio/dados/projetos/clube-letra-em-apps.json +84 -0
  33. package/lib/estudio/dados/projetos/clube-letra-globo-3d.json +111 -0
  34. package/lib/estudio/dados/projetos/clube-letra-nos-apps.json +146 -0
  35. package/lib/estudio/dados/projetos/clube-letra-seis-mundos.json +100 -0
  36. package/lib/estudio/dados/projetos/clube-lula-3.json +146 -0
  37. package/lib/estudio/dados/projetos/clube-morph.json +119 -0
  38. package/lib/estudio/dados/projetos/clube-motion-designer.json +115 -0
  39. package/lib/estudio/dados/projetos/clube-motion-pacote.json +112 -0
  40. package/lib/estudio/dados/projetos/clube-movimento.json +103 -0
  41. package/lib/estudio/dados/projetos/clube-not-like-us.json +117 -0
  42. package/lib/estudio/dados/projetos/clube-pinterest-inspiracao.json +86 -0
  43. package/lib/estudio/dados/projetos/clube-pixel-e-tinta.json +108 -0
  44. package/lib/estudio/dados/projetos/clube-ponte.json +118 -0
  45. package/lib/estudio/dados/projetos/clube-processo-edit-tonal.json +126 -0
  46. package/lib/estudio/dados/projetos/clube-processo-motion-100-ia.json +109 -0
  47. package/lib/estudio/dados/projetos/clube-promo.json +114 -0
  48. package/lib/estudio/dados/projetos/clube-proposito.json +115 -0
  49. package/lib/estudio/dados/projetos/clube-rankhog.json +128 -0
  50. package/lib/estudio/dados/projetos/clube-recursao.json +116 -0
  51. package/lib/estudio/dados/projetos/clube-revelar.json +118 -0
  52. package/lib/estudio/dados/projetos/clube-robo.json +125 -0
  53. package/lib/estudio/dados/projetos/clube-shelby-ep2.json +144 -0
  54. package/lib/estudio/dados/projetos/clube-shelby.json +97 -0
  55. package/lib/estudio/dados/projetos/clube-solar-eclipse.json +96 -0
  56. package/lib/estudio/dados/projetos/clube-soldier.json +128 -0
  57. package/lib/estudio/dados/projetos/clube-som-de-cena.json +108 -0
  58. package/lib/estudio/dados/projetos/clube-spotify-candidatura.json +112 -0
  59. package/lib/estudio/dados/projetos/clube-tda-app.json +118 -0
  60. package/lib/estudio/dados/projetos/clube-tda-ia.json +75 -0
  61. package/lib/estudio/dados/projetos/clube-telegram-chat.json +118 -0
  62. package/lib/estudio/dados/projetos/clube-telegram-ilha.json +106 -0
  63. package/lib/estudio/dados/projetos/clube-tennessee-nike.json +132 -0
  64. package/lib/estudio/dados/projetos/clube-tudo-se-move.json +112 -0
  65. package/lib/estudio/dados/projetos/clube-uiad.json +107 -0
  66. package/lib/estudio/dados/projetos/clube-viralgrow.json +108 -0
  67. package/lib/estudio/dados/projetos/clube-whose-side.json +140 -0
  68. package/lib/estudio/dados/projetos/clube-youtube-inscrever.json +108 -0
  69. package/lib/estudio/dados/projetos/elementos-do-site-em-movimento.json +49 -0
  70. package/lib/estudio/dados/projetos/explainer-narrado.json +51 -0
  71. package/lib/estudio/dados/projetos/lancamento-de-produto.json +51 -0
  72. package/lib/estudio/dados/projetos/motion-so-com-sons.json +50 -0
  73. package/lib/estudio/dados/projetos/titulo-em-after-effects.json +51 -0
  74. package/lib/estudio/dados/projetos/tour-de-saas.json +50 -0
  75. package/lib/estudio/dados/projetos-ae/antes-e-agora/ae/cenas.jsx +733 -0
  76. package/lib/estudio/dados/projetos-ae/antes-e-agora/assets/fazer_assets.py +88 -0
  77. package/lib/estudio/dados/projetos-ae/letra-em-apps/ae/cenas.jsx +402 -0
  78. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/preparar_assets.py +17 -0
  79. package/lib/estudio/dados/projetos-ae/letra-em-apps/assets/svg_para_ae.py +25 -0
  80. package/lib/estudio/dados/projetos-ae/letra-nos-apps/BRIEF-AGENTES.md +31 -0
  81. package/lib/estudio/dados/projetos-ae/letra-nos-apps/README.md +53 -0
  82. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ROTEIRO-CENAS.md +69 -0
  83. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/A-agenda.jsx +148 -0
  84. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/B-docs.jsx +174 -0
  85. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/C-alttab.jsx +113 -0
  86. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/D-spotify.jsx +118 -0
  87. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/E-notas.jsx +146 -0
  88. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/F-jornal1.jsx +144 -0
  89. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/G-google.jsx +153 -0
  90. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/H-mapa.jsx +176 -0
  91. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/I-imagens.jsx +132 -0
  92. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/J-jornal2.jsx +191 -0
  93. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/K-maps.jsx +184 -0
  94. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera-movimentos.jsx +48 -0
  95. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/camera.jsx +57 -0
  96. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/card-spotify.jsx +98 -0
  97. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/digitar.jsx +38 -0
  98. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/jornal-comum.jsx +196 -0
  99. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra.jsx +23 -0
  100. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/letra_automatica.py +83 -0
  101. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/montagem.jsx +74 -0
  102. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/render_html.py +13 -0
  103. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-agenda/util.jsx +162 -0
  104. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/w-midia-comum.jsx +114 -0
  105. package/lib/estudio/dados/projetos-ae/letra-nos-apps/ae/wg-util.jsx +360 -0
  106. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/F-jornal1/render_html.py +13 -0
  107. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/render_w-midia.py +26 -0
  108. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/inclin.py +30 -0
  109. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_d.py +36 -0
  110. package/lib/estudio/dados/projetos-ae/letra-nos-apps/assets/w-midia-ferramentas/medir_e.py +27 -0
  111. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ajustar_movimentos.py +61 -0
  112. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/ancorar.py +74 -0
  113. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/assar.py +47 -0
  114. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/camera3d_jornal.py +172 -0
  115. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/comparar.py +32 -0
  116. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/dados_ae_jornal.py +46 -0
  117. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/folha_contato.py +28 -0
  118. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/homografia_jornal.py +74 -0
  119. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/integrar.py +44 -0
  120. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/medir_quadro_a_quadro.py +32 -0
  121. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/mesa_2d.py +64 -0
  122. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/plot_cenas.py +11 -0
  123. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/retificar_quadros.py +38 -0
  124. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/assar_ab.py +52 -0
  125. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/w-agenda/medir_ab.py +59 -0
  126. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ajustar_h.py +111 -0
  127. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_corrige_cam.py +17 -0
  128. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_ecc_cadeia.py +80 -0
  129. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_finalizar_cam.py +65 -0
  130. package/lib/estudio/dados/projetos-ae/letra-nos-apps/camera/wg_mosaico.py +61 -0
  131. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/01-gramatica-e-roteiro.md +55 -0
  132. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/02-camera.md +66 -0
  133. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/03-montagem-no-ae.md +50 -0
  134. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/04-render-e-conferencia.md +43 -0
  135. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces/05-abertura-celular-mergulho.md +43 -0
  136. package/lib/estudio/dados/projetos-ae/letra-nos-apps/guia-do-metodo/tutorial-lyric-edit-interfaces.md +42 -0
  137. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/atraso_imagem.py +31 -0
  138. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/fundo.py +56 -0
  139. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/juntar.py +45 -0
  140. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/montar.py +177 -0
  141. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/rastreio.py +74 -0
  142. package/lib/estudio/dados/projetos-ae/letra-nos-apps/transicao/sincronia.py +18 -0
  143. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/ae/cenas.jsx +530 -0
  144. package/lib/estudio/dados/projetos-ae/pinterest-inspiracao/assets/fazer_assets.py +107 -0
  145. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/assets/pessoas/figurinha.py +46 -0
  146. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/BRIEF-A.md +83 -0
  147. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/audio.py +179 -0
  148. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/confere.py +20 -0
  149. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte1.js +1 -0
  150. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/dados/tempo-parte2.js +1 -0
  151. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/gerar_emojis.py +11 -0
  152. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/img/filmes/_indice.js +122 -0
  153. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/base.js +380 -0
  154. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/main.js +86 -0
  155. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1a.js +169 -0
  156. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1b.js +455 -0
  157. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p1c.js +389 -0
  158. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/js/p2.js +379 -0
  159. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/comp-a/video.py +62 -0
  160. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/README.md +65 -0
  161. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/oraculo_take.py +137 -0
  162. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/render_take.py +388 -0
  163. package/lib/estudio/dados/projetos-ae/processo-motion-100-ia/take-unico/take.js +235 -0
  164. package/lib/estudio/editor.js +290 -0
  165. package/lib/estudio/effectcraft.js +152 -0
  166. package/lib/estudio/exportar.js +181 -0
  167. package/lib/estudio/index.js +97 -2
  168. package/lib/estudio/kit-ae/base.jsx +1146 -0
  169. package/lib/estudio/kit-ae/cenas.jsx +395 -0
  170. package/lib/estudio/kit-ae/primitivas.jsx +911 -0
  171. package/lib/estudio/maquina-diretor.js +393 -0
  172. package/lib/estudio/maquina-elementos.js +134 -0
  173. package/lib/estudio/maquina-layout.js +198 -0
  174. package/lib/estudio/maquina-som.js +478 -0
  175. package/lib/estudio/maquina-trilha.js +160 -0
  176. package/lib/estudio/maquina-voz.js +429 -0
  177. package/lib/estudio/maquina.js +516 -0
  178. package/lib/estudio/projetos.js +72 -0
  179. package/lib/headless.js +71 -0
  180. package/lib/hooks.js +72 -0
  181. package/lib/limites.js +100 -0
  182. package/lib/local/cerebro.json +398 -12
  183. package/lib/local/entrada.js +46 -2
  184. package/lib/marca-kit.js +312 -0
  185. package/lib/mcp.js +286 -6
  186. package/lib/perigo.js +31 -3
  187. package/lib/planos.js +49 -19
  188. package/lib/preparar.js +111 -0
  189. package/lib/retomar.js +55 -0
  190. package/lib/sandbox.js +197 -0
  191. package/lib/skills.js +3 -1
  192. package/lib/tools.js +13 -4
  193. package/lib/ui.js +1 -1
  194. package/package.json +4 -2
  195. package/skills/after-effects/SKILL.md +52 -0
  196. package/skills/maquina-de-motion/SKILL.md +84 -0
@@ -0,0 +1,11 @@
1
+ """Empacota os Lottie do Telegram (assets/emoji-animado-telegram/*.json) em lib/emojis.js
2
+ para abrir por file:// sem fetch: window.EMOJI['u1f914'] = {...}. Rodar de novo ao baixar emoji novo."""
3
+ import json
4
+ from pathlib import Path
5
+ AQUI = Path(__file__).parent
6
+ SRC = AQUI.parent / "assets" / "emoji-animado-telegram"
7
+ out = ["window.EMOJI = window.EMOJI || {};"]
8
+ for f in sorted(SRC.glob("u*.json")):
9
+ out.append(f"EMOJI[{json.dumps(f.stem)}] = {f.read_text(encoding='utf-8')};")
10
+ (AQUI / "lib" / "emojis.js").write_text("\n".join(out), encoding="utf-8")
11
+ print(len(out) - 1, "emojis em lib/emojis.js")
@@ -0,0 +1,122 @@
1
+ window.FILMES_INFO = {
2
+ "ai-neon-prompt": {
3
+ "n": 42,
4
+ "fps": 30,
5
+ "w": 1000,
6
+ "h": 562,
7
+ "dur": 1.4,
8
+ "som": false,
9
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\ai-opener-645723\\previa.mp4",
10
+ "de": 4.3
11
+ },
12
+ "ai-opener-vertical": {
13
+ "n": 120,
14
+ "fps": 30,
15
+ "w": 440,
16
+ "h": 782,
17
+ "dur": 4.0,
18
+ "som": true,
19
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\ai-opener-645723\\previa.mp4",
20
+ "de": 4.0
21
+ },
22
+ "ask-ai-gemini": {
23
+ "n": 102,
24
+ "fps": 30,
25
+ "w": 960,
26
+ "h": 540,
27
+ "dur": 3.4,
28
+ "som": true,
29
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\ask-ai-workflow-391546\\previa.mp4",
30
+ "de": 1.0
31
+ },
32
+ "disney-arco": {
33
+ "n": 102,
34
+ "fps": 30,
35
+ "w": 960,
36
+ "h": 540,
37
+ "dur": 3.4,
38
+ "som": true,
39
+ "fonte": "<SUA_CENTRAL>\\07-videos\\projetos\\2026-10-16-processo-motion-100-ia\\assets\\produtos\\marcas\\disney-abertura-2011-sMyHnZ4lV54.mp4",
40
+ "de": 21.9
41
+ },
42
+ "emoji-ui-vertical": {
43
+ "n": 126,
44
+ "fps": 30,
45
+ "w": 470,
46
+ "h": 834,
47
+ "dur": 4.2,
48
+ "som": true,
49
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\emoji-ui-opener-195417\\previa.mp4",
50
+ "de": 13.6
51
+ },
52
+ "generico-dub": {
53
+ "n": 120,
54
+ "fps": 30,
55
+ "w": 1280,
56
+ "h": 720,
57
+ "dur": 4.0,
58
+ "som": false,
59
+ "fonte": "steventey-0d20e4-recriacao.mp4 (prompt-motion, 'feito por IA') 7,0-11,0 s",
60
+ "de": 7.0
61
+ },
62
+ "glass-hub": {
63
+ "n": 96,
64
+ "fps": 30,
65
+ "w": 960,
66
+ "h": 540,
67
+ "dur": 3.2,
68
+ "som": true,
69
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\ui-glass-interface-opener-552434\\previa.mp4",
70
+ "de": 12.4
71
+ },
72
+ "illdari-telegram": {
73
+ "n": 63,
74
+ "fps": 30,
75
+ "w": 912,
76
+ "h": 912,
77
+ "dur": 2.1,
78
+ "som": true,
79
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-17-illdari-ui-motion\\midia\\06-Db0sh1OshFr.mp4",
80
+ "de": 6.3
81
+ },
82
+ "illdari-upload": {
83
+ "n": 60,
84
+ "fps": 30,
85
+ "w": 912,
86
+ "h": 912,
87
+ "dur": 2.0,
88
+ "som": true,
89
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-17-illdari-ui-motion\\midia\\07-Db8VNNFMpsE.mp4",
90
+ "de": 2.7
91
+ },
92
+ "liquid-mini": {
93
+ "n": 72,
94
+ "fps": 30,
95
+ "w": 400,
96
+ "h": 226,
97
+ "dur": 2.4,
98
+ "som": false,
99
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\liquid-glass-opener-820335\\previa.mp4",
100
+ "de": 4.0
101
+ },
102
+ "roadster-voo": {
103
+ "n": 71,
104
+ "fps": 30,
105
+ "w": 960,
106
+ "h": 540,
107
+ "dur": 2.367,
108
+ "som": true,
109
+ "fonte": "<SUA_CENTRAL>\\07-videos\\projetos\\2026-10-16-processo-motion-100-ia\\assets\\produtos\\tesla-roadster\\tesla-The_Kickflip_Roadster-r5vsHHPVhJg.mp4",
110
+ "de": 32.45
111
+ },
112
+ "smartphone-ui-vertical": {
113
+ "n": 126,
114
+ "fps": 30,
115
+ "w": 470,
116
+ "h": 834,
117
+ "dur": 4.2,
118
+ "som": true,
119
+ "fonte": "<SUA_CENTRAL>\\05-pesquisa\\referencias\\2026-10-15-youmotion-templates\\dados\\ui-smartphone-interface-opener-962821\\previa.mp4",
120
+ "de": 19.6
121
+ }
122
+ };
@@ -0,0 +1,380 @@
1
+ /* base.js — kit da Variação A (aprovada pelo autor em 17/10/2026). Tudo é função pura do tempo t (motor take-único).
2
+ *
3
+ * A IDEIA: a fala vira uma letra de música rolando. Uma coluna única de texto grande (General Sans 120 px, à esquerda);
4
+ * cada palavra nasce do desfoque no tempo exato da voz; ícones, logos, emojis e vídeos entram DENTRO da linha (a linha
5
+ * abre espaço); a coluna sobe sem corte e a fala anterior fica cinza e desfocada. Os arquivos p1a/p1b/p1c/p2 só
6
+ * EMPILHAM conteúdo na coluna, na ordem da fala, com `linhas(fala, [...])`; o main.js diagrama e anima.
7
+ *
8
+ * linhas('06', [
9
+ * 'Passo um:', // linha = texto separado por espaço (cada palavra consome 1 da fala)
10
+ * 'não existe',
11
+ * ['a|vídeo', 'f|diferente', emoji('u1f914')], // linha como lista: palavras com marcas + itens
12
+ * { w: 'sem referência.', foco: 1000 }, // linha com opções (foco = altura na tela, gap = espaço antes)
13
+ * janela('glass-hub', { som: true, ate: 33.4 }), // bloco: janela com vídeo (registra o som para o mix)
14
+ * ]);
15
+ *
16
+ * Marcas de palavra (antes de "|"): a = azul (vídeo/estilo), o = terracota (IA/Claude), c = cinza, f = forte (chega
17
+ * maior e mais desfocada; use na palavra-chave). "100%#3" = a palavra mostrada vale 3 da fala ("cem por cento").
18
+ * Palavra que não é falada: palavra('texto', t).
19
+ * Itens dentro da linha: emoji(id) · logo(arquivo) · mini(filme) · icones([...]) · item({w, monta, seek}) (seu próprio).
20
+ * Sem `t`, o item entra 0,17 s antes da palavra seguinte da linha (ou logo depois da anterior).
21
+ * Blocos (ocupam a largura da coluna): janela(filme|[filmes]) · duo(premium, generico) · cardClaude({...}) · bloco({h, monta, seek}).
22
+ * Sem `t`, o bloco abre no fim da última palavra antes dele.
23
+ * Fora da coluna: cena(nome, t0, t1, monta, seek) numa camada por cima (#sobre); ocultaColuna(t0, t1) apaga a coluna.
24
+ * Som: ev(t, kind, pan, v) para efeitos (mapa padrão da Mesa; v 0,2–0,35 = sutil); janelas com som entram sozinhas no mix.
25
+ * Vídeo novo: python comp-a/video.py <fonte> --nome x --de 3 --dur 4 (quadros + som + índice).
26
+ *
27
+ * MODO PALCO (17/10, a partir de 29,6 s — ver o fim deste arquivo e o BRIEF-A.md): a tela é do ELEMENTO da frase e o texto
28
+ * vira legenda curta embaixo: legenda(fala, grupos) · palco(t0, t1, monta, seek) · passoHeroi(n, emoji, t0, t1) · pula(n).
29
+ */
30
+ const { clamp, lerp, seg } = TK;
31
+ const bz = TK.bezier;
32
+ const W = 1080, H = 1920;
33
+ const ILL = bz(0.16, 1, 0.3, 1), IO = bz(0.55, 0, 0.2, 1), ILLIN = bz(0.7, 0, 0.84, 0), POP = bz(0.2, 1.25, 0.3, 1);
34
+ const AS = '../assets/', LOGO = n => (/[\/]/.test(n) ? n : AS + 'logos/' + n), FIL = 'img/filmes/';
35
+ const LIN = [], CENAS = [], EVENTOS = [], VIDEOS = [], OCULTA = [], LOTS = [], FILMES = [];
36
+ const FOCO = 1330; // altura padrão (na tela) da linha que está sendo dita
37
+ const LH = 136; // altura da linha de texto
38
+
39
+ // ── contrato com o mix e cenas ──
40
+ const ev = (t, kind, pan = 0, v = 0.3) => { EVENTOS.push({ t: +(+t).toFixed(3), kind, pan, v }); return t; };
41
+ const cena = (nome, t0, t1, monta, seek) => { const c = { nome, t0, t1, monta, seek }; CENAS.push(c); return c; };
42
+ const ocultaColuna = (t0, t1, d = 0.45) => OCULTA.push({ t0, t1, d });
43
+
44
+ // ── DOM ──
45
+ function $(tag, cls, pai, css, html) { const e = document.createElement(tag); if (cls) e.className = cls; if (css) Object.assign(e.style, css); if (html != null) e.innerHTML = html; (pai || document.getElementById('palco')).appendChild(e); return e; }
46
+ function tf(e, { x = 0, y = 0, s = 1, r = 0, op = 1, blur = 0 } = {}) {
47
+ e.style.transform = `translate(${x.toFixed(2)}px,${y.toFixed(2)}px) rotate(${r.toFixed(3)}deg) scale(${s.toFixed(4)})`;
48
+ const v = clamp(op); e.style.opacity = v.toFixed(4); e.style.filter = blur > 0.1 ? `blur(${blur.toFixed(2)}px)` : 'none';
49
+ e.style.visibility = v < 0.003 ? 'hidden' : 'visible';
50
+ }
51
+ const deriva = (t, k = 0, amp = 4) => ({ x: amp * Math.sin(t * 0.6 + k * 2.1), y: amp * 0.7 * Math.cos(t * 0.47 + k * 1.3) });
52
+
53
+ // ── tempo das palavras ──
54
+ const norm = s => s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9]/g, '');
55
+ function PF(f, txt, k = 0, fim = false) {
56
+ if (typeof k === 'boolean') { fim = k; k = 0; }
57
+ const p = TEMPO.falas[f].palavras, alvo = norm(txt); let c = 0;
58
+ for (const w of p) if (norm(w.w) === alvo) { if (c++ === k) return fim ? w.e : w.s; }
59
+ c = 0; for (const w of p) if (norm(w.w).startsWith(alvo)) { if (c++ === k) return fim ? w.e : w.s; }
60
+ console.warn('palavra não achada', f, txt); return TEMPO.falas[f].t0;
61
+ }
62
+ const F0 = f => TEMPO.falas[f].t0, F1 = f => TEMPO.falas[f].t1, CN = n => TEMPO.cenas[n];
63
+
64
+ // ── entradas (curva única do @illdari: rápida no começo, cauda longa, sem quique) ──
65
+ function palavraVida(t, ts, forte) {
66
+ const u = seg(t, ts - 0.06, ts + (forte ? 0.42 : 0.30)), k = ILL(u);
67
+ return { op: clamp(u * 2.4), y: (1 - k) * (forte ? 10 : 30), s: forte ? lerp(1.16, 1, k) : lerp(0.93, 1, k), blur: Math.pow(1 - k, 2) * (forte ? 30 : 16) };
68
+ }
69
+ const nasce = (t, t0, d = 0.4) => { const u = seg(t, t0 - 0.04, t0 + d), k = ILL(u); return { u, k, op: clamp(u * 2.5), s: lerp(0.4, 1, POP(u)), y: (1 - k) * 30, blur: Math.pow(1 - k, 2) * 16 }; };
70
+
71
+ // ── Lottie e filmes ──
72
+ function emojiLot(pai, id, tam) {
73
+ const box = $('div', null, pai, { position: 'absolute', left: '0', top: '0', width: tam + 'px', height: tam + 'px' });
74
+ const d = EMOJI[id]; if (!d) { console.warn('emoji não achado', id); box.toca = () => 0; return box; }
75
+ const an = lottie.loadAnimation({ container: box, renderer: 'svg', loop: true, autoplay: false, animationData: JSON.parse(JSON.stringify(d)) });
76
+ const n = (d.op - d.ip) || 120; box.toca = (t, t0) => { const f = Math.max(0, t - t0) * (d.fr || 60); an.goToAndStop(f % n, true); }; LOTS.push(an); return box;
77
+ }
78
+ // filme = sequência de quadros de img/filmes/<nome>/ (feita pelo video.py); toca(t, t0, loop)
79
+ function filme(pai, nome) {
80
+ const I = (window.FILMES_INFO || {})[nome] || { n: 1, fps: 30 }; if (!(window.FILMES_INFO || {})[nome]) console.warn('filme sem índice', nome);
81
+ const e = $('img', null, pai); e.decoding = 'sync'; e.src = `${FIL}${nome}/0001.jpg`;
82
+ const f = { e, nome, n: I.n, fps: I.fps || 30, dur: I.dur || I.n / 30, at: 1, toca(t, t0, loop = true) { let i = Math.floor((t - t0) * f.fps); i = loop ? ((i % f.n) + f.n) % f.n : clamp(i, 0, f.n - 1); i += 1; if (i !== f.at) { f.at = i; e.src = `${FIL}${nome}/${String(i).padStart(4, '0')}.jpg`; } } };
83
+ FILMES.push(f); return f;
84
+ }
85
+ const durFilme = nome => ((window.FILMES_INFO || {})[nome] || {}).dur || 3;
86
+
87
+ // ── coluna: linhas e itens ──
88
+ const FILA = {}; const filaDe = f => FILA[f] || (FILA[f] = TEMPO.falas[f].palavras.slice());
89
+ function palavra(txt, t, marcas = '') { return { palavraLivre: true, txt, t, marcas }; }
90
+ function tokPalavra(f, s) {
91
+ if (s.palavraLivre) return mkPal(s.txt, s.t, s.t + 0.3, s.marcas);
92
+ const m = s.match(/^([aocf]+)\|(.+)$/); let fl = m ? m[1] : '', txt = m ? m[2] : s, n = 1;
93
+ const mn = txt.match(/^(.*)#(\d+)$/); if (mn) { txt = mn[1]; n = +mn[2]; }
94
+ const ws = filaDe(f).splice(0, n);
95
+ if (!ws.length) { console.warn('a fala ' + f + ' acabou em', txt); return mkPal(txt, F1(f), F1(f) + 0.3, fl); }
96
+ if (n === 1 && norm(ws[0].w) !== norm(txt)) console.warn('palavra diferente da fala', f, txt, '≠', ws[0].w);
97
+ return mkPal(txt, ws[0].s, ws[ws.length - 1].e, fl);
98
+ }
99
+ const mkPal = (txt, s, e, fl) => ({ txt, s, e, c: fl.includes('a') ? 'az' : fl.includes('o') ? 'or' : fl.includes('c') ? 'cinza' : '', forte: fl.includes('f') });
100
+
101
+ // item genérico dentro da linha: a linha abre `w` px e o item nasce ali. monta(dentro, it) · seek(t, it, k) (k = entrada 0–1)
102
+ function item(o) { return Object.assign({ item: 'custom', w: 124 }, o); }
103
+ function emoji(id, o = {}) {
104
+ return item(Object.assign({ w: 124, kind: 'pop',
105
+ monta(d, it) { it.lot = emojiLot(d, id, it.w); it.lot.style.top = '-6px'; },
106
+ seek(t, it) { const n = nasce(t, it.t); tf(it.in, { op: n.op, s: n.s, y: n.y, blur: n.blur }); it.lot.toca(t, it.t); } }, o));
107
+ }
108
+ function logo(arq, o = {}) { // logo oficial (assets/logos/) que gira de leve ao entrar; o.gira = false para parado
109
+ return item(Object.assign({ w: 112, tam: 96, kind: 'pop', gira: true,
110
+ monta(d, it) { const im = $('img', null, d, { position: 'absolute', left: ((it.w - it.tam) / 2) + 'px', top: ((116 - it.tam) / 2) + 'px', width: it.tam + 'px', height: it.tam + 'px', objectFit: 'contain' }); im.src = LOGO(arq); },
111
+ seek(t, it) { const n = nasce(t, it.t); tf(it.in, { op: n.op, s: lerp(0.3, 1, n.k), r: it.gira ? lerp(-120, 0, n.k) + 6 * Math.sin((t - it.t) * 1.6) : 0, blur: n.blur * 1.1 }); } }, o));
112
+ }
113
+ function mini(nome, o = {}) { // mini vídeo inline (sem som), em loop
114
+ return item(Object.assign({ w: 190, kind: 'mini',
115
+ monta(d, it) { const m = $('div', 'mini', d, { width: (it.w - 4) + 'px' }); it.f = filme(m, nome); },
116
+ seek(t, it) { const n = nasce(t, it.t); tf(it.in, { op: n.op, s: lerp(0.6, 1, n.k), blur: n.blur * 1.25 }); it.f.toca(t, it.t); } }, o));
117
+ }
118
+ // fila de ícones que nasce de um só e se abre (ex.: processo → ideia, Claude, voz, After Effects)
119
+ // ics: [{emoji:'u1f4a1', fundo:'#FFF6DC'} | {logo:'claude-symbol.svg', tam:56} | {logo:'after-effects-icon.svg', cheio:true}]
120
+ function icones(ics, o = {}) {
121
+ const passo = o.passo || 100;
122
+ return item(Object.assign({ w: 88 + passo * (ics.length - 1), kind: 'pop',
123
+ monta(d, it) { it.sqs = ics.map((ic, k) => { const q = $('div', 'sq', d, { left: '0px', zIndex: 20 - k, background: ic.fundo || '#fff' });
124
+ if (ic.emoji) { q.lot = emojiLot(q, ic.emoji, 76); q.lot.style.left = '6px'; q.lot.style.top = '6px'; }
125
+ else { const tam = ic.cheio ? 88 : (ic.tam || 56); const im = $('img', null, q, { left: ((88 - tam) / 2) + 'px', top: ((88 - tam) / 2) + 'px', width: tam + 'px', height: tam + 'px', objectFit: ic.cheio ? 'cover' : 'contain', transform: ic.cheio ? 'scale(1.16)' : '' }); im.src = LOGO(ic.logo); }
126
+ return q; }); },
127
+ seek(t, it) { tf(it.in, { op: 1 }); const n0 = nasce(t, it.t);
128
+ it.sqs.forEach((q, i) => { const ti = it.t + (i === 0 ? 0 : 0.22 + i * 0.09), ui = seg(t, ti - 0.04, ti + 0.42), ki = ILL(ui);
129
+ tf(q, { x: i === 0 ? 0 : lerp(0, i * passo, ki), op: i === 0 ? n0.op : clamp(ui * 3), s: i === 0 ? n0.s : lerp(0.85, 1, ki), blur: i === 0 ? n0.blur : Math.pow(1 - ki, 2) * 12, y: 2 * Math.sin((t + i) * 1.3) });
130
+ if (q.lot) q.lot.toca(t, it.t); }); } }, o));
131
+ }
132
+
133
+ // ── blocos (largura da coluna) ──
134
+ // bloco genérico: bloco({h, t, foco, monta(el, B), seek(t, B)}) — el é o retângulo 912 × h dentro da coluna
135
+ function bloco(o) { return Object.assign({ bloco: 'custom', h: 500 }, o); }
136
+ function abreJanela(t, el, t0, { d = 0.55, ex = 1, lado = 6 } = {}) { // a frase "abre" para o vídeo (recorte do centro + desfoque)
137
+ const u = seg(t, t0, t0 + d), k = ILL(u);
138
+ el.style.clipPath = `inset(${((1 - k) * 50).toFixed(2)}% ${((1 - k) * lado).toFixed(2)}% round 34px)`;
139
+ tf(el, { op: clamp(u * 3), s: lerp(0.93, 1, k) * ex, y: (1 - k) * 30, blur: Math.pow(1 - k, 2) * 22 });
140
+ return k;
141
+ }
142
+ // janela: um ou mais filmes (trocas = instantes de troca, pelo desfoque). som: true registra o som do vídeo para o mix;
143
+ // trilha: 'para' (a música sai enquanto o vídeo toca; padrão quando tem som) ou 'segue'. alvo: LUFS do vídeo antes do master
144
+ // (−20 sob a voz; −12,5 sem voz). ate: quando o som/quadro para (padrão: fim do último filme). quadrada: 912 × 912.
145
+ function janela(filmes, o = {}) {
146
+ const lista = [].concat(filmes);
147
+ return Object.assign({ bloco: 'janela', h: o.quadrada ? 912 : 513, foco: o.quadrada ? 960 : 1000, som: false, trocas: [], alvo: -20, kind: 'abre',
148
+ monta(el, B) {
149
+ B.jan = $('div', 'janela', el, { background: B.fundo || '#111' });
150
+ B.vs = lista.map(n => ({ c: $('div', null, B.jan, { position: 'absolute', inset: '0' }) })); B.vs.forEach((v, i) => v.f = filme(v.c, lista[i]));
151
+ B.ini = lista.map((n, i) => (i === 0 ? B.t : B.trocas[i - 1] - 0.05));
152
+ B.fim = B.ate ?? (B.ini[lista.length - 1] + durFilme(lista[lista.length - 1]));
153
+ if (B.som) lista.forEach((n, i) => VIDEOS.push({ nome: n, t0: B.ini[i], t1: i < lista.length - 1 ? B.trocas[i] + 0.12 : B.fim, trilha: B.trilha || 'para', alvo: B.alvo }));
154
+ },
155
+ seek(t, B) {
156
+ abreJanela(t, B.jan, B.t, { lado: B.quadrada ? 8 : 6, ex: B.quadrada ? 1 + 0.025 * seg(t, B.t, B.t + 4) : 1 });
157
+ B.vs.forEach((v, i) => { v.f.toca(Math.min(t, i < B.vs.length - 1 ? B.trocas[i] + 0.2 : B.fim), B.ini[i], false);
158
+ if (B.vs.length > 1) { const xa = i > 0 ? seg(t, B.trocas[i - 1] - 0.12, B.trocas[i - 1] + 0.22) : 1, xb = i < B.vs.length - 1 ? seg(t, B.trocas[i] - 0.12, B.trocas[i] + 0.22) : 0;
159
+ const op = ILL(xa) * (1 - ILL(xb)), bl = Math.sin(Math.PI * xa) * 18 * (1 - ILL(xa)) + Math.sin(Math.PI * xb) * 18 + (i > 0 ? (1 - ILL(xa)) * 10 : 0);
160
+ tf(v.c, { op, blur: bl, s: i > 0 ? lerp(1.08, 1, ILL(xa)) * (1 + 0.06 * ILL(xb)) : 1 + 0.06 * ILL(xb) }); } });
161
+ } }, o);
162
+ }
163
+ // duo: premium × genérico lado a lado (dois cards verticais de 440 × h). O premium abre em t, o genérico em tG, e em tCinza
164
+ // o genérico perde a cor e encolhe enquanto o premium cresce 3%. emojis: [premium, genérico] (Lottie, no canto de cada card)
165
+ function duo(premium, generico, o = {}) {
166
+ return Object.assign({ bloco: 'duo', h: 600, emojis: ['u1f929', 'u1f971'], kind: 'abre',
167
+ monta(el, B) {
168
+ B.p = $('div', 'janela', el, { inset: 'auto', left: '0', top: '0', width: '440px', height: B.h + 'px' }); B.fp = filme(B.p, premium);
169
+ B.g = $('div', 'janela', el, { inset: 'auto', left: '472px', top: '0', width: '440px', height: B.h + 'px', background: '#fff' }); B.fg = filme(B.g, generico);
170
+ B.ep = emojiLot($('div', null, el, { position: 'absolute', left: '330px', top: '-46px', width: '130px', height: '130px', zIndex: 5 }), B.emojis[0], 130);
171
+ B.eg = emojiLot($('div', null, el, { position: 'absolute', left: '802px', top: '-46px', width: '130px', height: '130px', zIndex: 5 }), B.emojis[1], 130);
172
+ B.tG = B.tG ?? B.t + 0.75; B.tCinza = B.tCinza ?? B.tG + 2;
173
+ ev(B.tG, 'abre', 0.3, 0.25); ev(B.t + 0.3, 'pop', -0.3, 0.2); ev(B.tCinza + 0.1, 'pop', 0.3, 0.2);
174
+ },
175
+ seek(t, B) {
176
+ const kg = ILL(seg(t, B.tCinza, B.tCinza + 0.5));
177
+ abreJanela(t, B.p, B.t, { d: 0.5, lado: 0, ex: 1 + 0.03 * kg }); abreJanela(t, B.g, B.tG, { d: 0.5, lado: 0, ex: 1 - 0.03 * kg });
178
+ B.fp.toca(t, B.t); B.fg.toca(Math.min(t, B.tCinza + 0.6), B.tG);
179
+ B.fg.e.style.filter = `grayscale(${kg.toFixed(3)}) contrast(${(1 - 0.15 * kg).toFixed(3)})`; B.g.style.opacity = (parseFloat(B.g.style.opacity) * (1 - 0.35 * kg)).toFixed(3);
180
+ const pe = (el, t0) => { const n = nasce(t, t0); tf(el.parentNode, { op: n.op, s: n.s, y: n.y + 4 * Math.sin(t * 2), blur: n.blur * 0.9 }); el.toca(t, t0); };
181
+ pe(B.ep, B.t + 0.3); pe(B.eg, B.tCinza + 0.1);
182
+ } }, o);
183
+ }
184
+ // card do claude.ai preso ao cursor: o cursor traz o card da direita (inclinando pela velocidade), solta no espaço aberto,
185
+ // digita o pedido, clica em enviar; a resposta corre em serifa e as ferramentas marcam. t = quando o card pousa (± 0,35 s).
186
+ function cardClaude(o = {}) {
187
+ return Object.assign({ bloco: 'card', h: 700, foco: 1010, kind: 'carry', titulo: 'Reel 100% IA', modelo: 'Opus 5.5', saudacao: 'E aí, o que vamos criar hoje?',
188
+ pedido: 'faz o motion dessa narração no estilo do Reel de referência',
189
+ resposta: 'Bora. Vou montar a composição em HTML no motor take-único, com a voz, a trilha e cada palavra no tempo da fala.',
190
+ ferramentas: ['<i>▤</i>Leu <code>roteiro.md</code> e <code>tempo.json</code>', '<i>✎</i>Criou <code>index.html</code> · 25 linhas animadas', '<i>▶</i>Renderizou <code>previa.mp4</code>'],
191
+ monta(el, B) {
192
+ const c = B.card = $('div', 'card', el);
193
+ c.innerHTML = `<div class="top"><img src="${LOGO('claude-symbol.svg')}" style="width:30px;height:30px"><span class="marca">Claude</span><span class="tit"><b>${B.titulo}</b> &nbsp;·&nbsp; Claude Code ⌄</span></div>
194
+ <div class="saud"><img src="${LOGO('claude-symbol.svg')}"><span>${B.saudacao}</span></div><div class="msgU">${B.pedido}</div><div class="resp"></div>
195
+ <div class="ferr">${B.ferramentas.map(f => `<div>${f}</div>`).join('')}</div><img class="asterisco" src="${LOGO('claude-symbol.svg')}">
196
+ <div class="caixa"><div class="txt"></div><div class="mais">+</div><div class="mod">${B.modelo} ⌄</div><div class="env"><svg width="20" height="20" viewBox="0 0 20 20"><path d="M10 16V4M4.5 9.5 10 4l5.5 5.5" stroke="#fff" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg></div></div>`;
197
+ B.q = s => c.querySelector(s); B.ferr = [...c.querySelectorAll('.ferr > div')];
198
+ B.cur = $('div', 'cursor', el, null, '<svg viewBox="0 0 32 32" width="64" height="64"><path d="M7 3.5 L7 25.5 L12.4 20.4 L16 28.6 L19.6 27 L16.1 19 L23.6 19 Z" fill="#111" stroke="#fff" stroke-width="2" stroke-linejoin="round"/></svg>');
199
+ B.anel = $('div', 'anel', el);
200
+ const T = B.t - 0.36; B.T = T; B.PEGA = { x: 640, y: 36 }; B.ENV = { x: 853, y: 647 };
201
+ B.CK = [{ t: T, x: 1250, y: 1150 }, { t: T + 0.7, x: 640, y: 36 }, { t: T + 0.82, x: 644, y: 38 }, { t: T + 1.12, x: 853, y: 647 }, { t: T + 1.3, x: 853, y: 647 }, { t: T + 2.0, x: 1230, y: 900 }];
202
+ B.clique = T + 1.2; B.tResp = B.clique + 0.22; B.dResp = Math.max(0.8, B.resposta.length / 95);
203
+ ev(T + 0.25, 'carry', 0.4, 0.22); ev(B.clique, 'clique', 0.3, 0.45);
204
+ },
205
+ seek(t, B) {
206
+ const cam = (tt) => { const K = B.CK; if (tt <= K[0].t) return K[0]; for (let i = 0; i < K.length - 1; i++) { const a = K[i], b = K[i + 1]; if (tt <= b.t) { const k = (i === 0 ? ILL : IO)(seg(tt, a.t, b.t)); return { x: lerp(a.x, b.x, k), y: lerp(a.y, b.y, k) }; } } return K[K.length - 1]; };
207
+ const T = B.T, cur = cam(t), atr = cam(t - 0.07), vx = cur.x - atr.x, vy = cur.y - atr.y, pousa = ILL(seg(t, T + 0.65, T + 1.0)), chega = seg(t, T, T + 0.32);
208
+ tf(B.card, { x: lerp(atr.x - B.PEGA.x, 0, pousa), y: lerp(atr.y - B.PEGA.y, 0, pousa), r: (1 - pousa) * (clamp(vx * 0.05, -7, 7) - clamp(vy * 0.012, -2, 2)), s: lerp(0.9, 1, ILL(seg(t, T, T + 0.9))), op: chega, blur: (1 - ILL(chega)) * 14 });
209
+ const pr = Math.exp(-Math.pow((t - B.clique) / 0.05, 2));
210
+ tf(B.cur, { x: cur.x - 7, y: cur.y - 4, s: 1 - 0.16 * pr, op: seg(t, T, T + 0.15) * (1 - seg(t, T + 1.75, T + 2.0)) });
211
+ const ua = seg(t, B.clique, B.clique + 0.7), r = lerp(8, 70, ILL(ua));
212
+ Object.assign(B.anel.style, { visibility: ua > 0 && ua < 1 ? 'visible' : 'hidden', left: (B.ENV.x - r) + 'px', top: (B.ENV.y - r) + 'px', width: 2 * r + 'px', height: 2 * r + 'px', opacity: (0.75 * (1 - ua)).toFixed(3), borderWidth: lerp(4, 1, ua) + 'px' });
213
+ const nDig = Math.floor(clamp((t - (T + 0.42)) / 0.66) * B.pedido.length), enviou = t >= B.clique + 0.04;
214
+ B.q('.caixa .txt').innerHTML = enviou || nDig === 0 ? '<span class="ph">Responder ao Claude…</span>' : B.pedido.slice(0, nDig) + '<span style="display:inline-block;width:2px;height:28px;background:#141413;vertical-align:-5px;margin-left:1px"></span>';
215
+ const us = seg(t, B.clique - 0.02, B.clique + 0.3); tf(B.q('.saud'), { op: 1 - us, y: -40 * ILL(us), blur: us * 10 });
216
+ const ub = seg(t, B.clique + 0.04, B.clique + 0.4), kb = ILL(ub); tf(B.q('.msgU'), { op: clamp(ub * 3), y: (1 - kb) * 300, s: lerp(0.9, 1, kb), blur: (1 - kb) * 8 });
217
+ B.q('.resp').textContent = B.resposta.slice(0, Math.floor(clamp((t - B.tResp) / B.dResp) * B.resposta.length));
218
+ B.ferr.forEach((f, i) => { const ui = seg(t, B.tResp + B.dResp - 0.37 + i * 0.3, B.tResp + B.dResp - 0.07 + i * 0.3), ki = ILL(ui); tf(f, { op: ui, y: (1 - ki) * 12, blur: (1 - ki) * 6 }); });
219
+ const pensa = t > B.clique && t < B.tResp + B.dResp + 1.1;
220
+ tf(B.q('.asterisco'), { op: seg(t, B.clique + 0.1, B.clique + 0.3), r: pensa ? (t - B.clique) * 140 : 0, s: pensa ? 1 + 0.12 * Math.sin((t - B.clique) * 9) : 1 });
221
+ } }, o);
222
+ }
223
+
224
+ // ── API de empilhar: linhas(fala, [...], {gap, grupo}) ──
225
+ // fala = '01'…; grupo = chave do apagamento (padrão = a fala; a fala anterior apaga quando o próximo grupo começa)
226
+ function linhas(f, lista, o = {}) {
227
+ let primeira = true;
228
+ for (const ent of lista) {
229
+ let L;
230
+ if (ent && ent.bloco) L = Object.assign({ g: o.grupo || f, f }, ent);
231
+ else {
232
+ const op = (ent && !Array.isArray(ent) && typeof ent === 'object' && !ent.item && !ent.palavraLivre) ? ent : { w: ent };
233
+ const raw = [].concat(op.w).flatMap(x => (typeof x === 'string' ? x.split(/\s+/).filter(Boolean) : [x])); // 'o seu' dentro de lista também vira 2 palavras
234
+ const w = raw.map(x => (typeof x === 'string' || x.palavraLivre) ? tokPalavra(f, x) : Object.assign({}, x));
235
+ // tempo dos itens sem t: 0,17 s antes da próxima palavra da linha; senão logo depois da anterior
236
+ w.forEach((x, i) => { if (!x.item || x.t != null) return; const nx = w.slice(i + 1).find(y => !y.item), pv = w.slice(0, i).reverse().find(y => !y.item);
237
+ x.t = nx ? nx.s - 0.17 : pv ? Math.max(pv.s + 0.15, pv.e - 0.04) : (LIN.length ? fimDe(LIN[LIN.length - 1]) : 0); });
238
+ L = Object.assign({ g: o.grupo || f, f }, op, { w });
239
+ }
240
+ if (primeira && o.gap != null && L.gap == null) L.gap = o.gap;
241
+ if (primeira && L.gap == null && LIN.length && LIN[LIN.length - 1].g !== L.g) L.gap = 70;
242
+ if (L.bloco && L.t == null) L.t = LIN.length ? fimDe(LIN[LIN.length - 1]) + 0.02 : 0;
243
+ if (o.some) L.some = o.some;
244
+ LIN.push(L); primeira = false;
245
+ }
246
+ }
247
+ const fimDe = L => L.bloco ? (L.t + 0.3) : Math.max(...L.w.map(x => (x.item ? x.t + 0.2 : x.e)));
248
+
249
+ // ── marcador de passo (17/10): "Passo n de 4" pequeno no topo da tela durante o passo, com 4 traços (os feitos em azul) ──
250
+ // contadorPasso(2, F0('10'), F0('14')) — entra pelo desfoque, o traço do passo acende; sai pelo desfoque no fim
251
+ function contadorPasso(n, t0, t1, total = 4) {
252
+ return cena('passo-' + n, t0, t1, (r, c) => {
253
+ c.box = $('div', null, r, { position: 'absolute', left: '84px', top: '128px', display: 'flex', alignItems: 'center', gap: '22px', font: '600 34px/1 GS', color: 'rgba(19,19,19,.62)', letterSpacing: '-0.01em' }, `<span>Passo ${n} de ${total}</span>`);
254
+ const tr = $('div', null, c.box, { display: 'flex', gap: '8px' }); c.tr = [...Array(total)].map((_, i) => $('i', null, tr, { display: 'block', width: '40px', height: '7px', borderRadius: '4px', background: 'rgba(19,19,19,.14)', overflow: 'hidden', position: 'relative' }, i < n ? '<b style="position:absolute;inset:0;background:var(--az);border-radius:4px;transform-origin:0 50%"></b>' : ''));
255
+ ev(t0 + 0.1, 'pop', 0, 0.18);
256
+ }, (t, c) => {
257
+ const e = palavraVida(t, t0, false), s = sai(t, t1);
258
+ tf(c.box, { op: e.op * s.op, y: e.y * 0.5 + s.y, blur: e.blur + s.blur });
259
+ c.tr.forEach((q, i) => { const b = q.firstChild; if (!b) return; const k = i < n - 1 ? 1 : ILL(seg(t, t0 + 0.3, t0 + 0.9)); b.style.transform = `scaleX(${k.toFixed(3)})`; });
260
+ });
261
+ }
262
+ // saída pelo desfoque, acelerando (para cenas/peças que saem antes da coluna as levar)
263
+ function sai(t, t1, d = 0.35, { dy = -30, blur1 = 20 } = {}) { const u = ILLIN(seg(t, t1 - d, t1)); return { u, op: 1 - u * u, y: u * dy, blur: u * blur1 }; }
264
+
265
+ // ═══════════════════════════════ MODO PALCO (17/10, a partir de 29,6 s) ═══════════════════════════════
266
+ // Retorno do autor: a coluna inteira virou "teleprompter". No modo palco a TELA É DO ELEMENTO que desenha a palavra-chave
267
+ // da frase, e o texto vira LEGENDA CURTA no terço de baixo (só o grupo da vez; o anterior sai inteiro, sobe e desfoca).
268
+ //
269
+ // legenda('07', ['Como você já deve saber,', 'não basta você ter', ['um super', 'f|prompt', emoji('u270d')], ...]);
270
+ // legenda('09'); // automático: corta por pontuação / ≤ 6 palavras / ≤ 30 letras
271
+ // legenda('09', null, { marcas: { referencia: 'af' }, itens: { gosto: emoji('u1f3a8') } }); // automático com cor e ícone
272
+ // palco(t0, t1, (el, P) => { ...monta... }, (t, P) => { ...anima... }); // elemento da frase (área y 200–1300)
273
+ // palco(t0, t1, promptRefs({ tRef }), { y: 760 }); // um bloco do kit (janela, duo, cardClaude, bloco) no palco
274
+ // palco(t0, t1, monta, seek, { esc: 1.25 }); // peça 25% maior (17/10: palco grande, centro real ≈ y 820)
275
+ // passoHeroi(1, 'u1f50d', t0, t1, { ate: F0('10') }); // emoji grande + nº do passo, tela limpa; depois o contador
276
+ // legenda('06', [pula(2), ...]) // "Passo um:" é dito no herói, sem legenda
277
+ //
278
+ // Grupos de legenda: mesmas marcas e itens da coluna (a| o| c| f|, 100%#3, emoji(), logo()...), os ícones saem a 66%.
279
+ // Cada grupo entra palavra a palavra pelo desfoque e sai inteiro quando o próximo grupo começa (ou 1 s depois da última
280
+ // palavra). ≤ 2 linhas (o confere avisa). Palco: a peça entra pelo desfoque (escala 0,94 → 1) e sai pelo desfoque; use
281
+ // { entra: false } / { sai: false } quando a peça anima a própria entrada/saída ou VIRA a próxima (morph). O palco inteiro
282
+ // tem câmera viva (deriva + zoom lento). PALCO_Y = centro do palco (760). Durante um passoHeroi, legenda e palco somem.
283
+ // A coluna (modo letra, 0–29,6 s) some com ocultaColuna(t0, t1) nos trechos em modo palco.
284
+ const LEGS = [], PALCOS = [], HEROIS = [], SILENCIO = [];
285
+ const PALCO_Y = 760, PALCO_DY = 60, LEG_Y = 1530, LEG_ESC = 0.66; // o palco desenha em volta de PALCO_Y; o kit desce tudo PALCO_DY (centro real ≈ 820, perto da legenda)
286
+ const pula = n => ({ pula: n }); // na legenda: n palavras faladas que não aparecem (ex.: "Passo um:" no herói do passo)
287
+ function partirFala(f, maxPal = 6, maxCar = 30) {
288
+ const ws = TEMPO.falas[f].palavras, gs = []; let g = [];
289
+ ws.forEach((w, i) => { g.push(w.w); const len = g.join(' ').length, pont = /[,.:;?!]$/.test(w.w);
290
+ if ((pont && g.length >= 2) || g.length >= maxPal || len >= maxCar || i === ws.length - 1) { gs.push(g.join(' ')); g = []; } });
291
+ return gs;
292
+ }
293
+ function legenda(f, grupos, o = {}) {
294
+ if (!grupos) grupos = partirFala(f, o.maxPal, o.maxCar).map(s => s.split(' ').flatMap(p => { const k = norm(p), mk = (o.marcas || {})[k], it = (o.itens || {})[k];
295
+ const w = mk ? mk + '|' + p : p; return it ? [w, Object.assign({}, it)] : [w]; }));
296
+ grupos.forEach(gr => {
297
+ const raw = [].concat(gr).flatMap(x => (typeof x === 'string' ? x.split(/\s+/).filter(Boolean) : [x]));
298
+ if (raw.length && raw.every(x => x.pula)) { raw.forEach(x => filaDe(f).splice(0, x.pula)); return; } // pula(n) sozinho: palavras faladas sem legenda
299
+ const w = raw.filter(x => !(x.pula && filaDe(f).splice(0, x.pula))).map(x => (typeof x === 'string' || x.palavraLivre) ? tokPalavra(f, x) : Object.assign({}, x));
300
+ w.forEach((x, i) => { if (!x.item || x.t != null) return; const nx = w.slice(i + 1).find(y => !y.item), pv = w.slice(0, i).reverse().find(y => !y.item);
301
+ x.t = nx ? nx.s - 0.17 : pv ? Math.max(pv.s + 0.15, pv.e - 0.04) : 0; });
302
+ LEGS.push({ f, w, t0: Math.min(...w.map(x => (x.item ? x.t : x.s))) - 0.06, fim: Math.max(...w.map(x => (x.item ? x.t + 0.3 : x.e))), y: o.y ?? LEG_Y });
303
+ });
304
+ if (o.ate) LEGS[LEGS.length - 1].ate = o.ate; // o último grupo sai até `ate` (ex.: antes da próxima cena)
305
+ }
306
+ function palco(t0, t1, monta, seek, o = {}) {
307
+ if (monta && monta.bloco) { const B = monta; o = seek || {}; B.t = B.t ?? t0 + 0.02;
308
+ return palco(t0, t1, (el, P) => { P.box = $('div', 'bloco', el, { left: '84px', top: ((o.y ?? PALCO_Y) - B.h / 2) + 'px', height: B.h + 'px' }); B.monta && B.monta(P.box, B); if (B.kind) ev(B.t, B.kind, 0, 0.25); },
309
+ (t, P) => B.seek && B.seek(t, B), Object.assign({ entra: false }, o)); }
310
+ const P = Object.assign({ t0, t1, monta, seek, entra: true, sai: true, esc: 1 }, o); PALCOS.push(P); return P; // esc: cresce a peça em volta de (540, PALCO_Y)
311
+ }
312
+ function passoHeroi(n, emojiId, t0, t1, o = {}) {
313
+ const H = Object.assign({ n, emojiId, t0, t1 }, o); HEROIS.push(H); SILENCIO.push([t0, t1]);
314
+ if (o.ate) contadorPasso(n, t1 - 0.05, o.ate);
315
+ ev(t0 + 0.05, 'abre', 0, 0.3); ev(t0 + 0.35, 'pop', 0.2, 0.25);
316
+ return H;
317
+ }
318
+ // motor do modo palco (o main.js chama monta/medir/seek)
319
+ window.PALCO_KIT = {
320
+ monta(sobre) {
321
+ this.lp = $('div', null, sobre, { position: 'absolute', inset: '0', transformOrigin: '540px 760px' });
322
+ this.lh = $('div', null, sobre, { position: 'absolute', inset: '0' });
323
+ this.ll = $('div', null, sobre, { position: 'absolute', inset: '0' });
324
+ PALCOS.forEach(P => { P.raiz = $('div', null, this.lp, { position: 'absolute', inset: '0', transformOrigin: `540px ${PALCO_Y}px` }); P.monta && P.monta(P.raiz, P); });
325
+ HEROIS.forEach(H => {
326
+ H.raiz = $('div', null, this.lh, { position: 'absolute', inset: '0' });
327
+ H.em = $('div', null, H.raiz, { position: 'absolute', left: (540 - 210) + 'px', top: (860 - 210) + 'px', width: '420px', height: '420px' }); H.lot = emojiLot(H.em, H.emojiId, 420);
328
+ H.badge = $('div', null, H.raiz, { position: 'absolute', left: (540 + 120) + 'px', top: (860 - 290) + 'px', width: '200px', height: '200px', borderRadius: '50%', background: 'var(--az)', color: '#fff', display: 'grid', placeItems: 'center', font: '700 136px/1 GS', letterSpacing: '-0.04em', boxShadow: '0 30px 60px -16px rgba(47,85,244,.55)' }, String(H.n));
329
+ if (H.rotulo) H.rot = $('div', null, H.raiz, { position: 'absolute', left: '0', right: '0', top: '1180px', textAlign: 'center', font: '600 72px/1 GS', letterSpacing: '-0.035em', color: 'var(--tinta)' }, H.rotulo);
330
+ });
331
+ LEGS.forEach((G, i) => {
332
+ const nx = LEGS[i + 1]; G.t1 = G.ate ?? (nx && nx.t0 - G.fim < 1.4 ? nx.t0 + 0.08 : G.fim + 1.0);
333
+ G.el = $('div', null, this.ll, { position: 'absolute', left: '60px', width: '960px', top: '0', textAlign: 'center', font: '600 80px/96px GS', letterSpacing: '-0.035em', color: 'var(--tinta)' });
334
+ G.w.forEach(w => {
335
+ if (!w.item) { w.el = $('span', 'p' + (w.c ? ' ' + w.c : ''), G.el, null, w.txt); return; }
336
+ w.el = $('span', 'it', G.el, { width: '0px', height: (116 * LEG_ESC) + 'px', marginTop: '-8px' });
337
+ const esc = $('div', null, w.el, { position: 'absolute', left: '0', top: '0', transformOrigin: '0 0', transform: `scale(${LEG_ESC})` });
338
+ w.in = $('div', null, esc, { position: 'absolute', left: '0', top: '0', width: w.w + 'px', height: '116px' });
339
+ w.monta && w.monta(w.in, w); if (w.kind && w.som !== false) ev(w.t, w.kind, 0, w.v ?? 0.2);
340
+ });
341
+ });
342
+ },
343
+ medir() { LEGS.forEach(G => { G.w.forEach(w => { if (w.item) w.el.style.width = (w.w * LEG_ESC + 4) + 'px'; }); const h = G.el.offsetHeight; G.el.style.top = (G.y - h / 2).toFixed(0) + 'px';
344
+ if (h > 96 * 2 + 8) console.warn('legenda com mais de 2 linhas (' + Math.round(h / 96) + ')', G.w.map(w => w.txt || w.item).join(' ')); G.w.forEach(w => { if (w.item) w.el.style.width = '0px'; }); }); },
345
+ seek(t) {
346
+ let sil = 0; for (const [a, b] of SILENCIO) sil = Math.max(sil, ILL(seg(t, a - 0.25, a + 0.1)) * (1 - ILL(seg(t, b - 0.1, b + 0.3))));
347
+ // câmera viva do palco: deriva lenta + respiração de escala
348
+ this.lp.style.transform = `translate(${(5 * Math.sin(t * 0.41)).toFixed(2)}px,${(PALCO_DY + 4 * Math.cos(t * 0.33)).toFixed(2)}px) scale(${(1.01 + 0.012 * Math.sin(t * 0.23)).toFixed(4)})`;
349
+ this.lp.style.opacity = (1 - sil).toFixed(3); this.ll.style.opacity = (1 - sil).toFixed(3);
350
+ PALCOS.forEach(P => { const vis = t >= P.t0 - 0.6 && t < P.t1 + 0.6; P.raiz.style.display = vis ? '' : 'none'; if (!vis) return;
351
+ const e = P.entra ? nasce(t, P.t0, 0.5) : { op: 1, s: 1, y: 0, blur: 0, k: 1 }, s = P.sai ? sai(t, P.t1, 0.4, { dy: -40, blur1: 24 }) : { op: 1, y: 0, blur: 0, u: 0 };
352
+ tf(P.raiz, { op: e.op * s.op, s: P.esc * (P.entra ? lerp(0.94, 1, e.k) : 1) * (1 + 0.04 * s.u), y: (P.entra ? (1 - e.k) * 24 : 0) + s.y, blur: e.blur + s.blur });
353
+ P.seek && P.seek(t, P); });
354
+ HEROIS.forEach(H => { const vis = t >= H.t0 - 0.3 && t < H.t1 + 0.6; H.raiz.style.display = vis ? '' : 'none'; if (!vis) return;
355
+ const e = nasce(t, H.t0, 0.55), s = sai(t, H.t1, 0.4, { dy: -60, blur1: 30 });
356
+ tf(H.em, { op: e.op * s.op, s: lerp(0.3, 1, POP(seg(t, H.t0 - 0.04, H.t0 + 0.55))) * (1 + 0.15 * s.u) * (1 + 0.015 * Math.sin((t - H.t0) * 3)), y: e.y + s.y, blur: e.blur + s.blur }); H.lot.toca(t, H.t0);
357
+ const b = seg(t, H.t0 + 0.25, H.t0 + 0.65); tf(H.badge, { op: clamp(b * 3) * s.op, s: lerp(0.2, 1, POP(b)), r: lerp(-25, 0, ILL(b)), y: s.y * 1.2, blur: Math.pow(1 - ILL(b), 2) * 14 + s.blur });
358
+ if (H.rot) { const r = palavraVida(t, H.t0 + 0.35, true); tf(H.rot, { op: r.op * s.op, y: r.y + s.y, s: r.s, blur: r.blur + s.blur }); } });
359
+ LEGS.forEach(G => { const vis = t >= G.t0 - 0.1 && t < G.t1 + 0.4; G.el.style.display = vis ? '' : 'none'; if (!vis) return;
360
+ const s = sai(t, G.t1, 0.32, { dy: -46, blur1: 22 }); tf(G.el, { op: s.op, y: s.y, blur: s.blur });
361
+ G.w.forEach(w => { if (!w.item) { tf(w.el, palavraVida(t, w.s, w.forte)); return; }
362
+ const ab = ILL(seg(t, w.t - 0.12, w.t + 0.30)); w.el.style.width = (ab * (w.w * LEG_ESC + 4)).toFixed(1) + 'px'; w.el.style.marginRight = (ab * 16).toFixed(1) + 'px'; w.seek && w.seek(t, w); }); });
363
+ },
364
+ };
365
+
366
+
367
+ // ═══ GANCHO / APRESENTAÇÃO (17/10, rodada C) ═══
368
+ // golpe(t, forca): soco de câmera no quadro inteiro (escala que salta e assenta) — no impacto da abertura e no fim de trailer.
369
+ // flash(t): clarão branco de câmera + som de obturador — a apresentação acabou, começa o tutorial.
370
+ // linhas(fala, [...], { some: t }): as linhas dessa fala somem inteiras (desfoque) até `t` (o gancho sai de cena).
371
+ const GOLPES = [];
372
+ const golpe = (t, forca = 1) => { GOLPES.push({ t, forca }); return t; };
373
+ // o: { som: false (sem obturador; o som vem de outro lugar), max: 0–1 (clarão mais fraco), dec: decaimento, forca: soco }
374
+ function flash(t, o = {}) {
375
+ if (o.som !== false) ev(t, 'obturador', 0, o.v ?? 0.6);
376
+ golpe(t, o.forca ?? 0.5);
377
+ const max = o.max ?? 1, dec = o.dec ?? 5.5;
378
+ return cena('flash', t - 0.2, t + 0.9, (r, c) => { c.w = $('div', null, document.getElementById('palco'), { position: 'absolute', inset: '0', background: '#fff', zIndex: 95, pointerEvents: 'none', opacity: '0' }); },
379
+ (tt, c) => { const op = max * (tt < t ? ILL(seg(tt, t - 0.07, t)) : Math.exp(-(tt - t) * dec)); c.w.style.opacity = clamp(op).toFixed(3); c.w.style.visibility = op < 0.003 ? 'hidden' : 'visible'; });
380
+ }
@@ -0,0 +1,86 @@
1
+ /* main.js — diagrama a coluna (LIN, empilhada pelos p*.js), anima a câmera que nunca para e cumpre o contrato do motor
2
+ * take-único: window.__seek / __ready / __meta / __events (com .videos = trechos de som dos vídeos mostrados, para o mix). */
3
+ (function () {
4
+ const palco = document.getElementById('palco'), col = document.getElementById('col'), vista = document.getElementById('vista'), sobre = document.getElementById('sobre');
5
+ const Q = new URLSearchParams(location.search);
6
+ const DUR = Q.has('ate') ? parseFloat(Q.get('ate')) : (window.DUR_PARTE || TEMPO.dur);
7
+ // fundo: branco quente com duas luzes muito fracas que derivam (azul e terracota)
8
+ const luzA = $('div', 'luz', palco, { width: '1300px', height: '1300px', left: '-420px', top: '-380px', background: 'radial-gradient(closest-side, rgba(47,85,244,.10), rgba(47,85,244,0))' });
9
+ const luzB = $('div', 'luz', palco, { width: '1400px', height: '1400px', left: '260px', top: '900px', background: 'radial-gradient(closest-side, rgba(217,119,87,.13), rgba(217,119,87,0))' });
10
+ palco.insertBefore(luzB, palco.firstChild); palco.insertBefore(luzA, palco.firstChild);
11
+
12
+ // ── diagrama: y de cada linha/bloco; t0 de cada um ──
13
+ let y = 0;
14
+ LIN.forEach((L, i) => {
15
+ y += i === 0 ? 0 : (L.gap ?? (L.bloco || LIN[i - 1].bloco ? 44 : 0));
16
+ L.y = y; L.h = L.h || LH; y += L.h;
17
+ L.t0 = L.bloco ? L.t : Math.min(...L.w.map(w => (w.item ? w.t : w.s)));
18
+ });
19
+ // grupo seguinte (para apagar): instante em que o próximo grupo começa
20
+ LIN.forEach((L, i) => { const nx = LIN.slice(i + 1).find(M => M.g !== L.g); L.apaga = nx ? nx.t0 : 1e9; });
21
+ // DOM
22
+ LIN.forEach(L => {
23
+ if (L.bloco) { L.el = $('div', 'bloco', col, { top: L.y + 'px', height: L.h + 'px' }); L.monta && L.monta(L.el, L); if (L.kind) ev(L.t, L.kind, 0, 0.25); return; }
24
+ L.el = $('div', 'linha', col, { top: L.y + 'px' });
25
+ L.w.forEach(w => {
26
+ if (!w.item) { w.el = $('span', 'p' + (w.c ? ' ' + w.c : ''), L.el, null, w.txt); return; }
27
+ w.el = $('span', 'it', L.el, { width: '0px' });
28
+ w.in = $('div', null, w.el, { position: 'absolute', left: '0', top: '0', width: w.w + 'px', height: '116px' });
29
+ w.monta && w.monta(w.in, w); if (w.kind && w.som !== false) ev(w.t, w.kind, 0, w.v ?? 0.22);
30
+ });
31
+ });
32
+ if (window.PALCO_KIT) PALCO_KIT.monta(sobre); // modo palco (base.js): palco, herói do passo, legenda curta
33
+ CENAS.forEach(c => { c.raiz = $('div', 'cena', sobre); c.monta && c.monta(c.raiz, c); });
34
+ const medeLinhas = () => LIN.forEach(L => { if (L.bloco) return; const fim = L.el.lastChild; const larg = L.w.reduce((a, w) => a + (w.item ? w.w + 22 : w.el.offsetWidth + 29), 0) - 29;
35
+ if (larg > 936) console.warn('linha larga (' + Math.round(larg) + ' px > 936)', L.w.map(w => w.txt || w.item).join(' ')); });
36
+
37
+ // ── câmera: a coluna sobe até a linha da vez com a curva única e cauda longa; nunca para (deriva lenta) ──
38
+ const ALVOS = LIN.map(L => ({ t: L.t0 - 0.12, v: (L.y + L.h / 2) - (L.foco ?? FOCO) }));
39
+ function camY(t) { if (!ALVOS.length) return 0; let v = ALVOS[0].v; for (let i = 1; i < ALVOS.length; i++) { const a = ALVOS[i]; if (t < a.t) break; v += (a.v - ALVOS[i - 1].v) * ILL(seg(t, a.t, a.t + 0.62)); } return v; }
40
+ // (a soma para no primeiro alvo futuro: alvos estão em ordem de tempo; se não estiverem, o diagrama avisa)
41
+ ALVOS.forEach((a, i) => { if (i && a.t < ALVOS[i - 1].t - 0.01) console.warn('linha fora de ordem no tempo', LIN[i]); });
42
+
43
+ function seek(t) {
44
+ const base = camY(t), cy = base + 2.5 * Math.sin(t * 0.6) + 0.35 * t, cx = 4 * Math.sin(t * 0.37 + 1);
45
+ col.style.transformOrigin = `456px ${(cy + 960).toFixed(1)}px`; // gira em torno do centro da TELA (com origem no topo da coluna, 0,25° deslocava a letra ~100 px no fim)
46
+ col.style.transform = `translate(${cx.toFixed(2)}px,${(-cy).toFixed(2)}px) rotate(${(0.25 * Math.sin(t * 0.29)).toFixed(3)}deg)`;
47
+ luzA.style.transform = `translate(${(60 * Math.sin(t * 0.21)).toFixed(1)}px,${(40 * Math.cos(t * 0.17)).toFixed(1)}px)`;
48
+ luzB.style.transform = `translate(${(-70 * Math.sin(t * 0.19 + 2)).toFixed(1)}px,${(-50 * Math.cos(t * 0.23)).toFixed(1)}px)`;
49
+ // coluna oculta (cenas de tela cheia)
50
+ let oc = 0; for (const o of OCULTA) oc = Math.max(oc, ILL(seg(t, o.t0 - o.d, o.t0)) * (1 - ILL(seg(t, o.t1, o.t1 + o.d))));
51
+ vista.style.opacity = (1 - oc).toFixed(3); vista.style.filter = oc > 0.01 ? `blur(${(oc * 20).toFixed(1)}px)` : 'none'; vista.style.visibility = oc > 0.995 ? 'hidden' : 'visible';
52
+ if (oc <= 0.995) LIN.forEach(L => {
53
+ const sy = L.y + L.h / 2 - cy, fora = sy < -L.h / 2 - 200 || sy > H + L.h / 2 + 200 || t < L.t0 - 1.5;
54
+ L.el.style.display = fora ? 'none' : ''; if (fora) return;
55
+ const d = ILL(seg(t, L.apaga - 0.25, L.apaga + 0.35));
56
+ const so = L.some ? ILL(seg(t, L.some - 0.45, L.some)) : 0; // linhas({ some }): a fala sai de cena inteira
57
+ if (so > 0.995) { L.el.style.display = 'none'; return; }
58
+ L.el.style.opacity = ((1 - 0.74 * d) * (1 - so)).toFixed(3); L.el.style.filter = (d + so) > 0.02 ? `blur(${(d * 2.2 + so * 16).toFixed(2)}px)` : 'none';
59
+ if (L.bloco) { L.seek && L.seek(t, L); return; }
60
+ L.w.forEach(w => {
61
+ if (!w.item) { tf(w.el, palavraVida(t, w.s, w.forte)); return; }
62
+ const ab = ILL(seg(t, w.t - 0.12, w.t + 0.30)); w.el.style.width = (ab * (w.w + 4)).toFixed(1) + 'px'; w.el.style.marginRight = (ab * 22).toFixed(1) + 'px';
63
+ w.seek && w.seek(t, w);
64
+ });
65
+ });
66
+ // golpe: soco de câmera no quadro inteiro
67
+ let gp = 0; for (const g of GOLPES) if (t >= g.t) gp += g.forca * Math.exp(-(t - g.t) * 6.5) * (1 - Math.exp(-(t - g.t) * 60));
68
+ const gs = gp > 0.002 ? `scale(${(1 + 0.05 * gp).toFixed(4)})` : '';
69
+ vista.style.transform = gs; vista.style.transformOrigin = '540px 960px'; sobre.style.transform = gs; sobre.style.transformOrigin = '540px 960px';
70
+ if (window.PALCO_KIT) PALCO_KIT.seek(t);
71
+ CENAS.forEach(c => { const vis = t >= c.t0 - 0.5 && t < c.t1 + 0.5; c.raiz.style.display = vis ? '' : 'none'; if (vis && c.seek) c.seek(t, c); });
72
+ const hud = document.getElementById('hud'); if (hud.style.display !== 'none') hud.textContent = t.toFixed(2) + ' s';
73
+ }
74
+
75
+ window.__meta = { dur: DUR, w: W, h: H, fps: 30, batidas: [], inFrame: {} };
76
+ window.__seek = async t => { seek(t); const vis = [...document.images].filter(i => i.offsetParent !== null && !i.complete);
77
+ if (vis.length) await Promise.all(vis.map(i => i.decode().catch(() => 0))); await new Promise(r => requestAnimationFrame(() => r())); return true; };
78
+ window.__track = t => ({}); window.__motion = (t0, t1) => 40;
79
+ window.__events = () => ({ dur: DUR, events: EVENTOS.filter(e => e.t < DUR).slice().sort((a, b) => a.t - b.t), videos: VIDEOS.filter(v => v.t0 < DUR) });
80
+ window.__ready = Promise.all([document.fonts.ready, ...['500', '600', '700'].map(w => document.fonts.load(`${w} 100px GS`)), document.fonts.load('500 20px Inter'),
81
+ ...[...document.images].map(i => i.decode ? i.decode().catch(() => 0) : 0)]).then(() => { medeLinhas(); if (window.PALCO_KIT) PALCO_KIT.medir(); seek(0); return true; });
82
+ if (Q.has('hud')) document.getElementById('hud').style.display = 'block';
83
+ if (Q.has('t')) window.__ready.then(() => window.__seek(parseFloat(Q.get('t'))));
84
+ if (Q.has('play')) window.__ready.then(() => { const au = Q.has('som') ? new Audio(window.MIX) : null; const de = parseFloat(Q.get('de') || 0); if (au) { au.currentTime = de; au.play(); } const t0 = performance.now() - de * 1000;
85
+ const loop = () => { seek(au ? au.currentTime : ((performance.now() - t0) / 1000) % DUR); requestAnimationFrame(loop); }; loop(); });
86
+ })();