primocode 10.15.2 → 10.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) 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 +74 -9
  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 +38 -8
  198. package/lib/app-chat.js +97 -0
  199. package/lib/catalogo.js +7 -1
  200. package/lib/chamada-torta.js +81 -0
  201. package/lib/editor-pro.js +786 -0
  202. package/lib/entrega.js +58 -0
  203. package/lib/estudio/conferencia-visual.js +76 -0
  204. package/lib/estudio/diretor.js +6 -1
  205. package/lib/estudio/exportar.js +3 -1
  206. package/lib/estudio/gosto/tela.html +1349 -0
  207. package/lib/estudio/gosto.js +305 -0
  208. package/lib/estudio/index.js +12 -1
  209. package/lib/estudio/kit-ae/base.jsx +119 -13
  210. package/lib/estudio/kit-ae/cenas.jsx +47 -15
  211. package/lib/estudio/kit-ae/guarda.jsx +274 -0
  212. package/lib/estudio/kit-ae/primitivas.jsx +180 -24
  213. package/lib/estudio/maquina-diretor.js +70 -12
  214. package/lib/estudio/maquina-foto.js +177 -0
  215. package/lib/estudio/maquina-guarda.js +207 -0
  216. package/lib/estudio/maquina-layout.js +22 -1
  217. package/lib/estudio/maquina.js +64 -11
  218. package/lib/estudio/referencias.js +1 -1
  219. package/lib/estudio/visao/visao.swift +97 -0
  220. package/lib/estudio/visao.js +60 -0
  221. package/lib/headless.js +15 -3
  222. package/lib/local/cerebro.json +285 -7
  223. package/lib/local/motor.js +5 -2
  224. package/lib/memoria-usuario.js +164 -0
  225. package/lib/nuvem.js +33 -2
  226. package/lib/passagem.js +50 -0
  227. package/lib/perigo.js +19 -11
  228. package/lib/protecao.js +134 -0
  229. package/lib/repeticao.js +1 -1
  230. package/lib/sandbox.js +57 -9
  231. package/lib/titulo-conversa.js +46 -0
  232. package/lib/tools.js +23 -4
  233. package/lib/ui.js +2 -2
  234. package/package.json +3 -2
  235. package/skills/editar-meu-video/SKILL.md +6 -2
  236. package/skills/editar-video/SKILL.md +113 -0
  237. package/studio/pessoa.py +52 -0
  238. package/voz/primo_voz/__main__.py +55 -40
  239. package/voz/primo_voz/app.py +500 -4
  240. package/voz/primo_voz/chat.html +1773 -0
  241. package/voz/primo_voz/conversa.py +0 -26
  242. package/voz/primo_voz/hud.html +8 -2
  243. package/voz/primo_voz/hud.py +129 -5
  244. package/voz/primo_voz/identidade.py +1 -1
  245. package/voz/primo_voz/painel_dados.py +134 -0
@@ -0,0 +1,1349 @@
1
+ <!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1,viewport-fit=cover"><style>:root{color-scheme:light;box-sizing:border-box;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}html{scroll-padding-top:env(safe-area-inset-top,0px)}body{margin:0;padding:0;font:14px -apple-system,BlinkMacSystemFont,sans-serif;background:#fff;color:#000}img{max-width:100%}[hidden]:not([hidden=until-found i]){display:none!important}</style></head><body>
2
+ <title>Seu gosto de motion · PrimoCode</title>
3
+ <style>
4
+ /* Layout: página de produto da Apple — título centrado, muito respiro, telinhas cinza claras, um azul só pra ação. */
5
+ :root {
6
+ --bg: #ffffff;
7
+ --palco: #ffffff;
8
+ --linha: #d2d2d7;
9
+ --giz: #1d1d1f;
10
+ --apagado: #6e6e73;
11
+ --ambar: #0071e3;
12
+ --ambar-hover: #0077ed;
13
+ --sobre: #ffffff;
14
+ --vermelho: #ff3b30;
15
+ --onda-ceu: #ffffff; --onda-clara: #f2fbff; --onda-media: #9fdcff; --onda-funda: #5ab4ff;
16
+ --sombra-card: 0 2px 6px rgba(0, 40, 90, .06), 0 12px 32px rgba(0, 40, 90, .08);
17
+ --c-brincalhao: var(--giz); --c-premium: var(--giz); --c-corporativo: var(--giz);
18
+ --c-energico: var(--giz); --c-cinema3d: var(--giz); --c-cinetico: var(--giz);
19
+ --f-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Helvetica, Arial, sans-serif;
20
+ --f-poster: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
21
+ --f-serifa: "New York", ui-serif, "Iowan Old Style", Georgia, serif;
22
+ --f-mono: ui-monospace, "SF Mono", Menlo, monospace;
23
+ }
24
+ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
25
+ --bg: #000000; --palco: #1c1c1e; --linha: #38383a; --giz: #f5f5f7; --apagado: #98989d;
26
+ --ambar: #2997ff; --ambar-hover: #47a6ff; --sobre: #ffffff; --vermelho: #ff453a; color-scheme: dark;
27
+ --onda-ceu: #03070f; --onda-clara: #b9ecff; --onda-media: #2f8fe8; --onda-funda: #5fd0ff;
28
+ --sombra-card: 0 2px 6px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .35); } }
29
+ :root[data-theme="dark"] {
30
+ --bg: #000000; --palco: #1c1c1e; --linha: #38383a; --giz: #f5f5f7; --apagado: #98989d;
31
+ --ambar: #2997ff; --ambar-hover: #47a6ff; --sobre: #ffffff; --vermelho: #ff453a; color-scheme: dark;
32
+ --onda-ceu: #03070f; --onda-clara: #b9ecff; --onda-media: #2f8fe8; --onda-funda: #5fd0ff;
33
+ --sombra-card: 0 2px 6px rgba(0, 0, 0, .4), 0 12px 32px rgba(0, 0, 0, .35); }
34
+ * { box-sizing: border-box; }
35
+ body {
36
+ background: var(--bg); color: var(--giz); font-family: var(--f-display); font-size: 17px; line-height: 1.47; letter-spacing: -.022em;
37
+ -webkit-font-smoothing: antialiased;
38
+ padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px));
39
+ padding-block: 56px 96px;
40
+ }
41
+ #ondas { position: fixed; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none;
42
+ background: linear-gradient(180deg, var(--onda-ceu) 35%, var(--onda-clara) 60%, var(--onda-media)); }
43
+ .palco { box-shadow: var(--sombra-card); }
44
+ .item, .tabela, pre, .passos span.ativo { box-shadow: var(--sombra-card); }
45
+ /* seleção: o círculo vazado aparece no hover; escolhido, fica azul com ✓ */
46
+ .selo { background: var(--bg); box-shadow: inset 0 0 0 1.5px var(--linha), 0 1px 3px rgba(0, 0, 0, .12); }
47
+ .selo::before { content: ""; }
48
+ .card:hover .selo, .card:focus-visible .selo { opacity: 1; transform: scale(1); }
49
+ .card[aria-pressed="true"] .selo { background: var(--ambar); box-shadow: none; }
50
+ .card[aria-pressed="true"] .selo::before { content: "✓"; }
51
+ @media (hover: none) { .selo { opacity: 1; transform: scale(1); } }
52
+ /* catálogo completo */
53
+ .catalogo { margin-top: 40px; display: grid; gap: 24px; }
54
+ .cat-topo { display: grid; gap: 6px; justify-items: center; text-align: center; }
55
+ .cat-topo h2 { margin: 0; font-size: clamp(26px, 3.4vw, 36px); font-weight: 600; letter-spacing: -.025em; }
56
+ .cat-grade { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 20px; align-items: start; }
57
+ .cat-grupo { background: var(--palco); border-radius: 18px; padding: 18px 20px; box-shadow: var(--sombra-card); }
58
+ .cat-grupo h3 { margin: 0 0 8px; font-size: 17px; font-weight: 600; display: flex; justify-content: space-between; align-items: baseline; }
59
+ .cat-grupo ul { list-style: none; margin: 0; padding: 0; }
60
+ .cat-grupo li { display: flex; justify-content: space-between; gap: 12px; padding: 9px 0; border-top: 1px solid var(--linha); font-size: 15px; }
61
+ .cat-grupo li small { color: var(--apagado); font-size: 13px; text-align: right; }
62
+ /* passo 2 e 3: prévia maior, campo no topo, sugestões com + */
63
+ .lista .item { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
64
+ @media (max-width: 700px) { .lista .item { grid-template-columns: minmax(0, 1fr); } }
65
+ .sug { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 18px; }
66
+ .sug button { all: unset; cursor: pointer; font-size: 14px; padding: 7px 14px; border-radius: 980px; color: var(--ambar); background: color-mix(in srgb, var(--ambar) 10%, var(--bg)); transition: background .15s ease, color .15s ease; }
67
+ .sug button::before { content: "+ "; font-weight: 600; }
68
+ .sug button:hover { background: color-mix(in srgb, var(--ambar) 18%, var(--bg)); }
69
+ .sug button:focus-visible { outline: 2px solid var(--ambar); outline-offset: 2px; }
70
+ .sug button[aria-pressed="true"] { background: var(--ambar); color: var(--sobre); }
71
+ .sug button[aria-pressed="true"]::before { content: "✓ "; }
72
+ .rot-chips { margin: 0 0 8px; font-size: 13px; color: var(--apagado); }
73
+ .mini-fila { display: flex; gap: 18px; justify-content: safe center; overflow-x: auto; padding: 8px 4px 18px; margin: -8px -4px -6px; scrollbar-width: thin; }
74
+ .mini-fila .palco { flex: none; width: min(280px, 78vw); border-radius: 18px; }
75
+ #passo2 .carrossel { padding-block: 10px; }
76
+ #passo2 .trilho { width: min(880px, calc(100vw - 32px)); }
77
+ #passo2 .acoes { margin-top: 18px !important; }
78
+ #passo2 .lista { gap: 16px; }
79
+ .lista .item-ops { grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 22px 26px; width: 100%; max-width: 860px; margin: 0 auto; border-radius: 20px; }
80
+ /* compacto: cada linha é o rótulo + quatro pílulas com um ícone pequeno animado */
81
+ .item-ops .grupo { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
82
+ .lista .item-ops h3 { flex: none; width: 128px; margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -.01em; color: var(--giz); }
83
+ .ops { flex: 1; display: flex; flex-wrap: wrap; gap: 10px; }
84
+ .op { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; gap: 10px; padding: 11px 22px 11px 15px; border-radius: 980px;
85
+ background: var(--bg); color: var(--giz); box-shadow: inset 0 0 0 1.5px var(--linha); font-size: 16px; font-weight: 500; white-space: nowrap;
86
+ transition: transform .3s cubic-bezier(.34, 1.56, .64, 1), background .2s ease, box-shadow .2s ease, color .2s ease; }
87
+ .op:hover { transform: translateY(-2px); box-shadow: inset 0 0 0 1.5px var(--ambar); }
88
+ .op:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
89
+ .op[aria-pressed="true"] { background: var(--ambar); color: var(--sobre); box-shadow: none; }
90
+ .menos .op:hover { box-shadow: inset 0 0 0 1.5px var(--vermelho); }
91
+ .menos .op[aria-pressed="true"] { background: var(--vermelho); box-shadow: none; }
92
+ .op .ic { transform: scale(.74); transform-origin: center; margin: -4px -6px; }
93
+ @media (max-width: 560px) { .lista .item-ops h3 { width: 100%; } }
94
+ /* os ícones: cada um faz o movimento que nomeia */
95
+ .ic { position: relative; width: 46px; height: 34px; color: var(--ambar); perspective: 120px; }
96
+ .menos .ic { color: var(--vermelho); }
97
+ .op[aria-pressed="true"] .ic { color: var(--sobre); }
98
+ .ic i { position: absolute; display: block; background: currentColor; }
99
+ .ic-rapido i { left: 6px; height: 4px; border-radius: 2px; animation: icRapido .7s cubic-bezier(.6, 0, .4, 1) infinite; }
100
+ .ic-rapido i:nth-child(1) { top: 5px; width: 30px; } .ic-rapido i:nth-child(2) { top: 15px; width: 22px; animation-delay: .12s; } .ic-rapido i:nth-child(3) { top: 25px; width: 27px; animation-delay: .24s; }
101
+ @keyframes icRapido { 0% { transform: translateX(-10px) scaleX(.4); opacity: 0; } 40% { opacity: 1; } 100% { transform: translateX(12px) scaleX(1); opacity: 0; } }
102
+ .ic-tres_d i { left: 11px; top: 5px; width: 24px; height: 24px; background: none; border: 3px solid currentColor; border-radius: 5px; box-sizing: border-box; animation: ic3d 2.6s linear infinite; }
103
+ @keyframes ic3d { from { transform: rotateX(25deg) rotateY(0deg); } to { transform: rotateX(25deg) rotateY(360deg); } }
104
+ .ic-quique i { left: 17px; top: 0; width: 12px; height: 12px; border-radius: 50%; animation: icQuique .8s infinite; }
105
+ @keyframes icQuique { 0% { transform: translateY(0); animation-timing-function: cubic-bezier(.55, 0, 1, .45); } 45% { transform: translateY(20px) scale(1.3, .7); animation-timing-function: cubic-bezier(0, .55, .45, 1); } 55% { transform: translateY(18px) scale(.9, 1.1); } 100% { transform: translateY(0); } }
106
+ .ic-calmo i { left: 13px; top: 7px; width: 20px; height: 20px; border-radius: 50%; animation: icCalmo 3.2s ease-in-out infinite; }
107
+ @keyframes icCalmo { 0%, 100% { transform: scale(.6); opacity: .45; } 50% { transform: scale(1.1); opacity: 1; } }
108
+ .ic-lento i:nth-child(1) { left: 4px; top: 21px; width: 38px; height: 3px; border-radius: 2px; opacity: .3; }
109
+ .ic-lento i:nth-child(2) { left: 4px; top: 16px; width: 12px; height: 12px; border-radius: 50%; animation: icLento 4s linear infinite; }
110
+ @keyframes icLento { from { transform: translateX(0); } to { transform: translateX(26px); } }
111
+ .ic-exagero i { left: 11px; top: 5px; width: 24px; height: 24px; clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%); animation: icExagero 1s cubic-bezier(.34, 1.56, .64, 1) infinite; }
112
+ @keyframes icExagero { 0%, 100% { transform: scale(.6) rotate(0deg); } 30% { transform: scale(1.45) rotate(-25deg); } 55% { transform: scale(.85) rotate(20deg); } }
113
+ .ic-flash i { left: 9px; top: 3px; width: 28px; height: 28px; border-radius: 6px; animation: icFlash .5s steps(1) infinite; }
114
+ @keyframes icFlash { 0% { opacity: 1; } 50% { opacity: .1; } }
115
+ .ic-tremido i { left: 8px; top: 14px; width: 30px; height: 6px; border-radius: 3px; animation: icTremido .25s steps(4) infinite; }
116
+ @keyframes icTremido { 0% { transform: translate(0, 0); } 25% { transform: translate(-3px, 2px); } 50% { transform: translate(3px, -2px); } 75% { transform: translate(-2px, -1px); } }
117
+ @media (prefers-reduced-motion: reduce) { .ic i { animation: none !important; } }
118
+ /* salvo no PrimoCode */
119
+ .salvo { margin: 0; display: inline-flex; gap: 10px; align-items: center; font-weight: 600; font-size: 17px; }
120
+ .mundo { position: absolute; left: 50%; top: 50%; width: 320px; height: 200px; margin: -100px 0 0 -160px; transform-style: preserve-3d; transform-origin: center; }
121
+ #catGrade { display: grid; grid-template-columns: minmax(0, 1fr); gap: 40px; }
122
+ .cat-sec { display: grid; gap: 16px; }
123
+ .cat-cab { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--linha); }
124
+ .cat-cab h3 { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -.02em; }
125
+ .cat-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 18px; }
126
+ .tile .rotulo b { font-size: 15px; font-weight: 500; }
127
+ .salvo span { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; background: var(--ambar); color: var(--sobre); font-size: 13px; }
128
+ /* carrossel infinito: colunas de dois motions deslizam por transform; o carrossel vaza até a borda da janela
129
+ e esmaece nas pontas, então o motion que sai vai sumindo em vez de ser cortado pelo bloco */
130
+ body { overflow-x: clip; }
131
+ .carrossel { position: relative; width: 100vw; margin-left: calc(50% - 50vw); padding-block: 18px; overflow: hidden;
132
+ -webkit-mask-image: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent); mask-image: linear-gradient(90deg, transparent, #000 16%, #000 84%, transparent); }
133
+ .trilho { position: relative; width: min(1040px, calc(100vw - 32px)); margin: 0 auto; touch-action: pan-y; }
134
+ .trilho:focus-visible { outline: none; }
135
+ .trilho:focus-visible .coluna.vis { outline: 2px solid var(--ambar); outline-offset: 6px; border-radius: 22px; }
136
+ .coluna { position: absolute; left: 0; top: 0; display: grid; gap: 20px; will-change: transform, opacity;
137
+ transition: transform .9s cubic-bezier(.65, 0, .25, 1), opacity .9s cubic-bezier(.65, 0, .25, 1), filter .9s ease; }
138
+ .coluna.salta { transition: none; }
139
+ .coluna.longe { pointer-events: none; }
140
+ @media (prefers-reduced-motion: reduce) { .coluna { transition: opacity .2s linear; } }
141
+ .seta { all: unset; box-sizing: border-box; position: absolute; top: calc(50% - 24px); z-index: 4; width: 48px; height: 48px; border-radius: 50%; cursor: pointer;
142
+ display: grid; place-items: center; color: var(--giz); background: color-mix(in srgb, var(--palco) 80%, transparent);
143
+ -webkit-backdrop-filter: blur(14px) saturate(1.5); backdrop-filter: blur(14px) saturate(1.5);
144
+ box-shadow: var(--sombra-card), inset 0 0 0 1px var(--linha); transition: opacity .2s ease, transform .2s ease; }
145
+ .seta svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
146
+ .seta:hover { transform: scale(1.08); }
147
+ .seta:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
148
+ .seta-volta { left: max(16px, calc(50% - 560px)); } .seta-vai { right: max(16px, calc(50% - 560px)); }
149
+ .rotulo .tipo { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--ambar); }
150
+ .wrap { max-width: 1040px; margin: 0 auto; display: grid; gap: 48px; }
151
+ header { display: grid; gap: 12px; justify-items: center; text-align: center; }
152
+ .passos { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; font-size: 12px; color: var(--apagado); letter-spacing: 0; }
153
+ .passos span { padding: 4px 12px; border-radius: 999px; }
154
+ .passos span.ativo { color: var(--giz); background: var(--palco); font-weight: 600; }
155
+ h1 { font-size: clamp(32px, 5.4vw, 56px); line-height: 1.07; font-weight: 600; letter-spacing: -.028em; margin: 8px 0 0; text-wrap: balance; max-width: 18ch; }
156
+ .sub { color: var(--apagado); margin: 0; max-width: 46ch; font-size: clamp(17px, 2vw, 21px); line-height: 1.38; }
157
+
158
+ .grade { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
159
+ @media (max-width: 900px) { .grade { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
160
+ @media (max-width: 600px) { .grade { grid-template-columns: minmax(0, 1fr); } }
161
+
162
+ .card { all: unset; box-sizing: border-box; display: grid; gap: 14px; cursor: pointer; border-radius: 22px; position: relative; transition: transform .3s ease; }
163
+ .card:hover { transform: scale(1.012); }
164
+ .card:focus-visible { outline: 2px solid var(--ambar); outline-offset: 4px; }
165
+ .card[aria-pressed="true"] .palco { box-shadow: 0 0 0 2px var(--ambar); }
166
+ .selo { position: absolute; top: 12px; right: 12px; z-index: 3; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
167
+ font-size: 0; color: var(--sobre); background: var(--ambar); opacity: 0; transform: scale(.6); transition: opacity .2s ease, transform .25s ease; }
168
+ .selo::before { content: "✓"; font-size: 14px; font-weight: 600; }
169
+ .card[aria-pressed="true"] .selo { opacity: 1; transform: scale(1); }
170
+ .palco { position: relative; aspect-ratio: 16 / 10; max-width: 100%; background: var(--palco); border-radius: 18px; overflow: hidden; perspective: 900px; display: grid; place-items: center; transition: box-shadow .2s ease; }
171
+ .cena { position: relative; width: 100%; height: 100%; display: grid; place-items: center; transform-style: preserve-3d; }
172
+ .rotulo { display: grid; gap: 2px; padding: 0 4px; text-align: center; }
173
+ .rotulo b { font-size: 19px; font-weight: 600; color: var(--giz); }
174
+ .rotulo small { color: var(--apagado); font-size: 14px; letter-spacing: -.01em; }
175
+
176
+ /* peças das cenas */
177
+ .palavra { font-weight: 700; font-size: clamp(30px, 4.2vw, 44px); letter-spacing: -.03em; color: var(--cor); will-change: transform, opacity, filter; }
178
+ .bola { position: absolute; width: 30px; height: 30px; border-radius: 50%; background: var(--cor); bottom: 16%; left: 20%; }
179
+ .cartao { position: absolute; width: 30%; aspect-ratio: 3 / 2; border: 2px solid var(--cor); border-radius: 12px; }
180
+ .letras { display: flex; gap: .02em; font-family: var(--f-poster); font-weight: 800; letter-spacing: -.04em; font-size: clamp(38px, 5.4vw, 58px); color: var(--cor); }
181
+ .letras span { display: inline-block; will-change: transform; }
182
+ .faisca { position: absolute; width: 18px; height: 2px; background: var(--cor); border-radius: 1px; opacity: 0; }
183
+
184
+ .acoes { display: flex; gap: 20px; align-items: center; justify-content: center; flex-wrap: wrap; }
185
+ .btn { all: unset; cursor: pointer; font-size: 17px; font-weight: 400; padding: 12px 24px; border-radius: 980px; background: var(--ambar); color: var(--sobre); transition: background .2s ease, opacity .2s ease; }
186
+ .btn:hover { background: var(--ambar-hover); }
187
+ .btn:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
188
+ .btn[disabled] { opacity: .3; cursor: not-allowed; }
189
+ /* botão grande, sem brilho; o Continuar do passo 1 só aparece depois da primeira escolha */
190
+ .btn-grande { font-size: 19px; font-weight: 600; padding: 17px 42px; transition: background .2s ease, transform .2s ease, opacity .35s ease; }
191
+ .btn-grande:hover { transform: translateY(-1px); }
192
+ #ir2 { transition: background .2s ease, opacity .4s ease, transform .4s cubic-bezier(.34, 1.56, .64, 1); }
193
+ #ir2[disabled] { opacity: 0; transform: translateY(10px) scale(.96); pointer-events: none; }
194
+ .link { all: unset; cursor: pointer; color: var(--ambar); font-size: 17px; }
195
+ .link:hover { text-decoration: underline; }
196
+ /* botão secundário de vidro: legível em cima da onda nos dois temas */
197
+ .btn-vidro { all: unset; cursor: pointer; font-size: 17px; padding: 16px 30px; border-radius: 980px; color: var(--giz);
198
+ background: color-mix(in srgb, var(--palco) 72%, transparent); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
199
+ box-shadow: inset 0 0 0 1px var(--linha); transition: background .2s ease; }
200
+ .btn-vidro:hover { background: var(--palco); }
201
+ .btn-vidro:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; }
202
+ .link:focus-visible { outline: 2px solid var(--ambar); outline-offset: 2px; }
203
+ .conta { font-size: 14px; color: var(--apagado); font-variant-numeric: tabular-nums; }
204
+
205
+ /* passo 2 e 3 */
206
+ .lista { display: grid; gap: 20px; }
207
+ .item { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 32px; align-items: center; padding: 24px; background: var(--palco); border-radius: 22px; }
208
+ .item .palco { background: var(--bg); }
209
+ @media (max-width: 700px) { .item { grid-template-columns: minmax(0, 1fr); gap: 20px; } }
210
+ .item h3 { margin: 0 0 14px; font-size: 24px; font-weight: 600; letter-spacing: -.02em; color: var(--giz); }
211
+ .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
212
+ .chip { all: unset; cursor: pointer; font-size: 14px; padding: 8px 16px; border-radius: 980px; background: var(--bg); color: var(--giz); box-shadow: inset 0 0 0 1px var(--linha); transition: background .15s ease, box-shadow .15s ease, color .15s ease; }
213
+ .chip:hover { box-shadow: inset 0 0 0 1px var(--apagado); }
214
+ .chip:focus-visible { outline: 2px solid var(--ambar); outline-offset: 2px; }
215
+ .chip[aria-pressed="true"] { background: var(--ambar); color: var(--sobre); box-shadow: none; }
216
+ .nao .chip[aria-pressed="true"] { background: var(--vermelho); }
217
+ .nao h3 { color: var(--giz) !important; }
218
+ textarea { width: 100%; min-height: 72px; resize: vertical; background: var(--bg); color: var(--giz); border: 1px solid var(--linha); border-radius: 12px; padding: 12px 14px; font: inherit; font-size: 15px; }
219
+ textarea:focus-visible { outline: none; border-color: var(--ambar); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ambar) 25%, transparent); }
220
+
221
+ /* ficha final */
222
+ .ficha { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 32px; }
223
+ @media (max-width: 800px) { .ficha { grid-template-columns: minmax(0, 1fr); } }
224
+ .ficha .acoes { justify-content: flex-start; }
225
+ .resumo { font-size: clamp(21px, 2.6vw, 28px); line-height: 1.3; font-weight: 600; letter-spacing: -.02em; margin: 0; max-width: 32ch; }
226
+ .tabela { font-size: 15px; background: var(--palco); border-radius: 18px; padding: 8px 20px; margin: 0; }
227
+ .tabela div { display: flex; justify-content: space-between; gap: 16px; padding: 13px 0; border-bottom: 1px solid var(--linha); }
228
+ .tabela div:last-child { border-bottom: 0; }
229
+ .tabela dt { color: var(--apagado); }
230
+ .tabela dd { margin: 0; text-align: right; color: var(--giz); font-weight: 500; font-variant-numeric: tabular-nums; }
231
+ .barra { height: 4px; background: var(--linha); border-radius: 2px; overflow: hidden; margin-top: 8px; }
232
+ .barra i { display: block; height: 100%; background: var(--ambar); border-radius: 2px; transition: width .9s cubic-bezier(.4,0,.2,1); }
233
+ pre { margin: 0; overflow-x: auto; background: var(--palco); border-radius: 18px; padding: 16px 20px; font-family: var(--f-mono); font-size: 12px; color: var(--apagado); max-height: 240px; letter-spacing: 0; }
234
+ /* tela final: só a frase, o check e o botão */
235
+ .fim { display: grid; justify-items: center; gap: 22px; text-align: center; margin-top: -8px; }
236
+ .fim-check svg { width: 96px; height: 96px; fill: none; stroke: var(--ambar); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; overflow: visible; }
237
+ .fim-check circle { stroke-dasharray: 145; stroke-dashoffset: 145; }
238
+ .fim-check path { stroke-width: 4; stroke-dasharray: 34; stroke-dashoffset: 34; }
239
+ #passo4:not([hidden]) .fim-check { animation: fimPula .7s cubic-bezier(.34, 1.56, .64, 1) .55s both; }
240
+ #passo4:not([hidden]) .fim-check circle { animation: fimTraco .6s cubic-bezier(.65, 0, .35, 1) forwards; }
241
+ #passo4:not([hidden]) .fim-check path { animation: fimTraco .35s cubic-bezier(.65, 0, .35, 1) .5s forwards; }
242
+ @keyframes fimTraco { to { stroke-dashoffset: 0; } }
243
+ @keyframes fimPula { 0% { transform: scale(1); } 40% { transform: scale(1.16); } 100% { transform: scale(1); } }
244
+ @media (prefers-reduced-motion: reduce) { .fim-check circle, .fim-check path { stroke-dashoffset: 0; animation: none !important; } .fim-check { animation: none !important; } }
245
+ .fim-sub { margin: 0; color: var(--apagado); font-size: clamp(17px, 2vw, 21px); }
246
+ .pular { font-size: 14px; color: var(--apagado); }
247
+ .erro { margin: 14px auto 0; max-width: 46ch; text-align: center; color: var(--vermelho); font-size: 15px; }
248
+ .nota { color: var(--apagado); font-size: 14px; margin: 0; max-width: 60ch; }
249
+ code { font-family: var(--f-mono); font-size: .92em; }
250
+ </style>
251
+
252
+ <canvas id="ondas" aria-hidden="true"></canvas>
253
+ <div class="wrap">
254
+ <header>
255
+ <h1 id="titulo">Qual movimento tem a sua cara?</h1>
256
+ </header>
257
+
258
+ <section id="passo1">
259
+ <div class="carrossel" id="carrossel">
260
+ <button class="seta seta-volta" id="setaVolta" aria-label="Voltar" aria-controls="grade"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg></button>
261
+ <div class="trilho" id="grade" tabindex="0" aria-label="Todos os motions"></div>
262
+ <button class="seta seta-vai" id="setaVai" aria-label="Avançar" aria-controls="grade"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg></button>
263
+ </div>
264
+ <div class="acoes" style="margin-top:36px"><button class="btn btn-grande" id="ir2" disabled>Continuar</button></div>
265
+ <div class="acoes" style="margin-top:14px"><button class="link pular" id="pular">pular por agora</button></div>
266
+ </section>
267
+
268
+ <section id="passo2" hidden>
269
+ <div class="lista" id="lista2"></div>
270
+ <div class="acoes" style="margin-top:28px"><button class="btn btn-grande" id="ir3">Continuar</button><button class="btn-vidro" id="volta1">Voltar</button></div>
271
+ <p class="erro" id="erro" role="alert" hidden></p>
272
+ </section>
273
+
274
+ <section id="passo3" hidden>
275
+ <div class="lista">
276
+ <div class="item nao" style="--cor:var(--giz)">
277
+ <div class="palco" id="palcoNao"></div>
278
+ <div>
279
+ <h3>O que você NÃO quer ver?</h3>
280
+ <textarea id="naoTexto" placeholder="algo que te incomoda em motion? (opcional)"></textarea>
281
+ <div class="sug" id="sugNao"></div>
282
+ <p class="rot-chips">Marque o que você não quer</p>
283
+ <div class="chips" id="chipsNao"></div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ <div class="acoes" style="margin-top:22px"><button class="btn" id="salvar">Salvar meu gosto</button><button class="link" id="volta2">voltar</button></div>
288
+ </section>
289
+
290
+ <section id="passo4" hidden>
291
+ <div class="fim">
292
+ <div class="fim-check" aria-hidden="true"><svg viewBox="0 0 52 52"><circle cx="26" cy="26" r="23"/><path d="M15.5 27l7 7 14-15"/></svg></div>
293
+ <p class="fim-sub" id="fimSub" aria-live="polite">Tudo salvo no seu PrimoCode.</p>
294
+ <button class="btn btn-grande" id="finalizar">Voltar pro PrimoCode</button>
295
+ </div>
296
+ </section>
297
+ </div>
298
+
299
+ <script>
300
+ const TOKEN = '__TOKEN__';
301
+ window.GOSTO = __DADOS__; // o servidor manda os estilos que ele conhece; a tela só mostra esses
302
+ const ESTILOS = [
303
+ { id: 'brincalhao', nome: 'Brincalhão', frase: 'quica e passa do ponto', ritmo: 'rapido', cameras: ['pulso','orbita'], trocas: ['giro','encolhe','abre'], tipografias: ['arredondada','manuscrita'], overshoot: .18 },
304
+ { id: 'premium', nome: 'Premium', frase: 'lento, liso, elegante', ritmo: 'lento', cameras: ['cinema','deriva'], trocas: ['desfoque','dissolve','cortina'], tipografias: ['serifa_fina','editorial'], overshoot: 0 },
305
+ { id: 'corporativo', nome: 'Direto', frase: 'empurra e corta seco', ritmo: 'medio', cameras: ['trilho','push'], trocas: ['empurra','persiana','corte'], tipografias: ['geometrica','tecnica'], overshoot: .04 },
306
+ { id: 'energico', nome: 'Enérgico', frase: 'chicote, punch no beat', ritmo: 'rapido', cameras: ['chicote','pulso'], trocas: ['chicote','tremor','zoom','corte'], tipografias: ['pesada','poster'], overshoot: .25 },
307
+ { id: 'cinema3d', nome: 'Cinema 3D', frase: 'gira em profundidade', ritmo: 'medio', cameras: ['orbita','cinema','push'], trocas: ['giro','zoom','empurra'], tipografias: ['poster','editorial'], overshoot: .1 },
308
+ { id: 'cinetico', nome: 'Letra que dança', frase: 'cada letra entra sozinha', ritmo: 'rapido', cameras: ['pulso','push'], trocas: ['corte','chicote','encolhe'], tipografias: ['poster','pesada','tecnica'], overshoot: .15 },
309
+ ];
310
+ const GOSTEI = [['rapido','rápido'],['tres_d','3D'],['quique','com quique'],['calmo','calmo']];
311
+ const NAO_QUERO = [['lento','devagar'],['exagero','exagerado'],['flash','flash'],['tremido','tremido']];
312
+ const RITMO_NOME = { rapido: 'rápido', medio: 'médio', lento: 'lento' };
313
+ const SUGESTOES = {
314
+ brincalhao: ['o quique no fim', 'a bolinha amassando', 'parece desenho animado', 'leve e divertido'],
315
+ premium: ['a entrada lenta', 'o desfoque saindo', 'parece marca de luxo', 'a letra com serifa'],
316
+ direto: ['sem enrolação', 'o corte seco', 'tudo alinhado', 'parece apresentação de empresa'],
317
+ energico: ['o soco na batida', 'a letra inclinada', 'muita energia', 'parece trailer'],
318
+ cinema3d: ['a profundidade', 'o giro do cartão', 'parece filme', 'a palavra vindo do fundo'],
319
+ cinetico: ['cada letra sozinha', 'o giro do O', 'ritmo de música', 'letra grande e pesada'],
320
+ };
321
+ const SUG_NAO = ['elemento colado na borda', 'música fraca', 'efeito demais', 'texto difícil de ler', 'todo vídeo igual'];
322
+
323
+ /* pílulas "+": um toque põe a frase no campo, outro tira; o texto continua editável */
324
+ function sugestoes(frases, ta, aoMudar) {
325
+ return el('div', { class: 'sug' }, frases.map((f) => {
326
+ const b = el('button', { 'aria-pressed': 'false', text: f });
327
+ b.addEventListener('click', () => {
328
+ const on = b.getAttribute('aria-pressed') !== 'true';
329
+ b.setAttribute('aria-pressed', String(on));
330
+ const partes = ta.value.split(',').map((s) => s.trim()).filter((s) => s && s !== f);
331
+ if (on) partes.push(f);
332
+ ta.value = partes.join(', ').slice(0, 280);
333
+ aoMudar(ta.value);
334
+ });
335
+ return b;
336
+ }));
337
+ }
338
+ const parar = matchMedia('(prefers-reduced-motion: reduce)').matches;
339
+
340
+ const estado = { escolhidos: new Map(), nao: new Set(), naoTexto: '', extras: new Set() };
341
+ const $ = (s) => document.querySelector(s);
342
+ const el = (tag, attrs = {}, kids = []) => { const e = document.createElement(tag); for (const [k, v] of Object.entries(attrs)) { if (k === 'text') e.textContent = v; else if (k === 'style') e.style.cssText = v; else e.setAttribute(k, v); } kids.forEach((k) => e.appendChild(k)); return e; };
343
+
344
+ /* ── as cenas ─────────────────────────────────────────────────────────────
345
+ Cada telinha é um "mundo" de 320×200 px escalado pro tamanho do card, então as
346
+ distâncias valem igual no celular e no desktop. Easing vai em cada keyframe
347
+ (o easing geral da animação distorceria a linha do tempo inteira). */
348
+ const OVER = 'cubic-bezier(.34,1.56,.64,1)', LISO = 'cubic-bezier(.4,0,.2,1)', MD3 = 'cubic-bezier(.2,0,0,1)',
349
+ CAI = 'cubic-bezier(.55,0,1,.45)', SOBE = 'cubic-bezier(0,.55,.45,1)', CHICOTE = 'cubic-bezier(.75,0,.15,1)';
350
+ const loop = (n, kf, opt) => n.animate(kf, { iterations: Infinity, ...opt });
351
+ const TINTA = 'var(--cor)', VEU = 'color-mix(in srgb, var(--cor) 9%, transparent)';
352
+ const T = (x = 0, y = 0, r = 0, sx = 1, sy = sx) => `translate(${x}px,${y}px) rotate(${r}deg) scale(${sx},${sy})`;
353
+
354
+ function mundo(palco) {
355
+ const m = el('div', { class: 'mundo' });
356
+ palco.appendChild(m);
357
+ const ajustar = () => { m.style.scale = String((palco.clientWidth || 320) / 320); };
358
+ ajustar();
359
+ try { new ResizeObserver(ajustar).observe(palco); } catch { /* sem ResizeObserver: fica no tamanho inicial */ }
360
+ return m;
361
+ }
362
+ const peca = (pai, estilo, texto) => {
363
+ const e = el('div', texto == null ? { style: 'position:absolute;' + estilo } : { style: 'position:absolute;' + estilo, text: texto });
364
+ pai.appendChild(e); return e;
365
+ };
366
+ const palavra = (pai, estilo, texto = 'PRIMO') => peca(pai, `font-family:var(--f-display);font-weight:800;letter-spacing:-.04em;color:${TINTA};white-space:nowrap;line-height:1;` + estilo, texto);
367
+ function letras(pai, estilo, texto = 'PRIMO') {
368
+ const w = peca(pai, `display:flex;font-family:var(--f-display);font-weight:800;letter-spacing:-.04em;color:${TINTA};line-height:1;white-space:pre;` + estilo);
369
+ return texto.split('').map((ch) => { const s = el('span', { text: ch, style: 'display:inline-block;will-change:transform' }); w.appendChild(s); return s; });
370
+ }
371
+
372
+ function montarCena(id, palco) {
373
+ const m = mundo(palco), A = [];
374
+ if (parar) {
375
+ const p = palavra(m, 'left:0;right:0;top:78px;text-align:center;font-size:44px');
376
+ A.push(loop(p, [{ opacity: .35 }, { opacity: 1 }], { duration: 2400, direction: 'alternate' }));
377
+ return A;
378
+ }
379
+ const CENAS = { ...NOVAS_CENAS, brincalhao: cenaBrincalhao, premium: cenaPremium, corporativo: cenaDireto, energico: cenaEnergico, cinema3d: cenaCinema3d, cinetico: cenaCinetico, nao: cenaNao };
380
+ (CENAS[id] || cenaPremium)(m, A);
381
+ return A;
382
+ }
383
+
384
+ /* Brincalhão: a bolinha cai, quica três vezes, estoura em confete e vira a palavra. */
385
+ function cenaBrincalhao(m, A) {
386
+ const D = 3200;
387
+ const bola = peca(m, `left:146px;top:-34px;width:28px;height:28px;border-radius:50%;background:${TINTA}`);
388
+ A.push(loop(bola, [
389
+ { transform: T(0, 0), opacity: 1, easing: CAI },
390
+ { transform: T(0, 150, 0, 1.3, .65), offset: .14, easing: SOBE },
391
+ { transform: T(0, 80, 0, .85, 1.15), offset: .24, easing: CAI },
392
+ { transform: T(0, 150, 0, 1.2, .75), offset: .33, easing: SOBE },
393
+ { transform: T(0, 118, 0, .95, 1.05), offset: .4, easing: CAI },
394
+ { transform: T(0, 150, 0, 1.1, .85), offset: .46 },
395
+ { transform: T(0, 140, 0, 1.6), opacity: 1, offset: .5 },
396
+ { transform: T(0, 140, 0, 0), opacity: 0, offset: .53 },
397
+ { transform: T(0, 0, 0, 0), opacity: 0 }], { duration: D }));
398
+ for (let i = 0; i < 10; i++) {
399
+ const a = (i / 10) * Math.PI * 2, r = 70 + (i % 3) * 18, t = i % 2 ? 6 : 9;
400
+ const c = peca(m, `left:156px;top:118px;width:${t}px;height:${t}px;border-radius:${i % 3 ? '50%' : '2px'};background:${TINTA};opacity:0`);
401
+ A.push(loop(c, [
402
+ { transform: T(0, 0, 0, 0), opacity: 0 },
403
+ { transform: T(0, 0, 0, 0), opacity: 0, offset: .5 },
404
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .51, easing: 'cubic-bezier(.1,.8,.3,1)' },
405
+ { transform: T(Math.cos(a) * r, Math.sin(a) * r * .7 + 20, i * 50, .6), opacity: 0, offset: .74 },
406
+ { transform: T(0, 0, 0, 0), opacity: 0 }], { duration: D }));
407
+ }
408
+ letras(m, 'left:0;right:0;top:66px;justify-content:center;font-size:52px').forEach((s, i) => {
409
+ const t = .5 + i * .028, rot = [-14, 10, -6, 12, -10][i];
410
+ A.push(loop(s, [
411
+ { transform: T(0, 40, 0, 0) },
412
+ { transform: T(0, 40, 0, 0), offset: t, easing: OVER },
413
+ { transform: T(0, -18, rot, 1.3), offset: t + .07 },
414
+ { transform: T(0, 4, -rot / 3, .94), offset: t + .12 },
415
+ { transform: T(0, 0, 0, 1), offset: t + .17 },
416
+ { transform: T(0, 0, 0, 1), offset: .86, easing: 'ease-out' },
417
+ { transform: T(0, -6, 0, 1.1), offset: .9, easing: 'ease-in' },
418
+ { transform: T(0, 30, 0, 0), offset: .97 },
419
+ { transform: T(0, 30, 0, 0) }], { duration: D }));
420
+ });
421
+ const card = peca(m, `left:238px;top:22px;width:46px;height:32px;border-radius:9px;border:2.5px solid ${TINTA}`);
422
+ A.push(loop(card, [
423
+ { transform: T(0, 0, -30, 0) }, { transform: T(0, 0, -30, 0), offset: .62, easing: OVER },
424
+ { transform: T(0, 0, 14, 1.25), offset: .7 }, { transform: T(0, 0, -6, 1), offset: .76 },
425
+ { transform: T(0, 0, 3, 1), offset: .82 }, { transform: T(0, 0, 0, 1), offset: .88, easing: 'ease-in' },
426
+ { transform: T(0, 0, 30, 0), offset: .96 }, { transform: T(0, 0, 30, 0) }], { duration: D }));
427
+ }
428
+
429
+ /* Premium: a câmera recua devagar, a palavra sai do desfoque apertando o espaçamento e um brilho corre por ela. */
430
+ function cenaPremium(m, A) {
431
+ const D = 4400;
432
+ A.push(loop(m, [{ transform: 'scale(1.14)' }, { transform: 'scale(1)' }], { duration: D, easing: LISO }));
433
+ const fonte = 'left:0;right:0;top:56px;text-align:center;font-family:var(--f-serifa);font-style:italic;font-weight:400;font-size:60px;letter-spacing:-.01em;';
434
+ const p = palavra(m, fonte, 'Primo');
435
+ A.push(loop(p, [
436
+ { opacity: 0, filter: 'blur(16px)', letterSpacing: '.35em', transform: 'translateY(10px)', easing: LISO },
437
+ { opacity: 1, filter: 'blur(0px)', letterSpacing: '-.01em', transform: 'translateY(0)', offset: .42 },
438
+ { opacity: 1, filter: 'blur(0px)', letterSpacing: '-.01em', transform: 'translateY(0)', offset: .84, easing: LISO },
439
+ { opacity: 0, filter: 'blur(12px)', letterSpacing: '.1em', transform: 'translateY(-6px)' }], { duration: D }));
440
+ const brilho = palavra(m, fonte + 'color:transparent;background:linear-gradient(105deg,transparent 42%,rgba(255,255,255,.95) 50%,transparent 58%);background-size:300% 100%;-webkit-background-clip:text;background-clip:text', 'Primo');
441
+ A.push(loop(brilho, [
442
+ { backgroundPosition: '100% 0', opacity: 0 }, { backgroundPosition: '100% 0', opacity: 1, offset: .46, easing: LISO },
443
+ { backgroundPosition: '0% 0', opacity: 1, offset: .74 }, { backgroundPosition: '0% 0', opacity: 0, offset: .8 }, { backgroundPosition: '0% 0', opacity: 0 }], { duration: D }));
444
+ const linha = peca(m, `left:110px;top:134px;width:100px;height:1px;background:${TINTA}`);
445
+ A.push(loop(linha, [
446
+ { transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .3, easing: LISO }, { transform: 'scaleX(1)', offset: .56 },
447
+ { transform: 'scaleX(1)', offset: .84, easing: LISO }, { transform: 'scaleX(0)', opacity: 0 }], { duration: D }));
448
+ const tag = peca(m, `left:0;right:0;top:148px;text-align:center;font-family:var(--f-mono);font-size:9px;letter-spacing:.6em;color:${TINTA}`, 'ESTÚDIO · MMXXVI');
449
+ A.push(loop(tag, [
450
+ { opacity: 0, letterSpacing: '1em' }, { opacity: 0, letterSpacing: '1em', offset: .46, easing: LISO },
451
+ { opacity: .7, letterSpacing: '.6em', offset: .66 }, { opacity: .7, letterSpacing: '.6em', offset: .84 }, { opacity: 0, letterSpacing: '.6em' }], { duration: D }));
452
+ }
453
+
454
+ /* Direto: o título sobe por máscara, as barras entram empurrando, o anel enche e tudo sai numa cortina seca. */
455
+ function cenaDireto(m, A) {
456
+ const D = 2800;
457
+ const g = peca(m, 'left:0;top:0;width:320px;height:200px');
458
+ A.push(loop(g, [
459
+ { clipPath: 'inset(0 0 0 0)' }, { clipPath: 'inset(0 0 0 0)', offset: .82, easing: MD3 },
460
+ { clipPath: 'inset(0 0 0 100%)', offset: .92 }, { clipPath: 'inset(0 0 0 100%)' }], { duration: D }));
461
+ const titulo = palavra(g, 'left:36px;top:26px;font-size:36px');
462
+ A.push(loop(titulo, [
463
+ { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(16px)' }, { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(16px)', offset: .03, easing: MD3 },
464
+ { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)', offset: .18 }, { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)' }], { duration: D }));
465
+ [.7, .45, .86].forEach((v, i) => {
466
+ const y = 80 + i * 32;
467
+ const trilho = peca(g, `left:36px;top:${y}px;width:168px;height:22px;border-radius:7px;background:${VEU}`);
468
+ const cheio = peca(trilho, `left:0;top:0;height:22px;width:${v * 100}%;border-radius:7px;background:${TINTA};transform-origin:left`);
469
+ A.push(loop(trilho, [
470
+ { transform: 'translateX(320px)' }, { transform: 'translateX(320px)', offset: .08 + i * .05, easing: MD3 },
471
+ { transform: 'translateX(0)', offset: .24 + i * .05 }, { transform: 'translateX(0)' }], { duration: D }));
472
+ A.push(loop(cheio, [
473
+ { transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .26 + i * .05, easing: MD3 },
474
+ { transform: 'scaleX(1)', offset: .46 + i * .05 }, { transform: 'scaleX(1)' }], { duration: D }));
475
+ });
476
+ const NS = ['http:', '', 'www.w3.org', '2000', 'svg'].join('/');
477
+ const svg = document.createElementNS(NS, 'svg');
478
+ svg.setAttribute('viewBox', '0 0 64 64');
479
+ svg.style.cssText = 'position:absolute;left:222px;top:86px;width:68px;height:68px;overflow:visible;color:var(--cor)';
480
+ const circ = (attrs) => { const c = document.createElementNS(NS, 'circle'); Object.entries({ cx: 32, cy: 32, r: 26, fill: 'none', stroke: 'currentColor', 'stroke-width': 6, ...attrs }).forEach(([k, v]) => c.setAttribute(k, v)); svg.appendChild(c); return c; };
481
+ circ({ opacity: .12 });
482
+ const arco = circ({ 'stroke-linecap': 'round', 'stroke-dasharray': 163.4, 'stroke-dashoffset': 163.4, transform: 'rotate(-90 32 32)' });
483
+ const txt = document.createElementNS(NS, 'text');
484
+ Object.entries({ x: 32, y: 37, 'text-anchor': 'middle', 'font-size': 14, 'font-weight': 700, fill: 'currentColor', 'font-family': '-apple-system, sans-serif' }).forEach(([k, v]) => txt.setAttribute(k, v));
485
+ txt.textContent = '75%'; svg.appendChild(txt);
486
+ g.appendChild(svg);
487
+ A.push(loop(svg, [
488
+ { transform: 'scale(.5)', opacity: 0 }, { transform: 'scale(.5)', opacity: 0, offset: .16, easing: OVER },
489
+ { transform: 'scale(1)', opacity: 1, offset: .28 }, { transform: 'scale(1)', opacity: 1 }], { duration: D }));
490
+ A.push(loop(arco, [
491
+ { strokeDashoffset: 163.4 }, { strokeDashoffset: 163.4, offset: .28, easing: MD3 },
492
+ { strokeDashoffset: 41 }, { strokeDashoffset: 41 }].map((k, i) => (i === 2 ? { ...k, offset: .56 } : k)), { duration: D }));
493
+ }
494
+
495
+ /* Enérgico: listras correndo, rastros do chicote, a palavra entra inclinada, dois socos de câmera com faísca e um selo carimbado. */
496
+ function cenaEnergico(m, A) {
497
+ const D = 1800;
498
+ const fundo = peca(m, 'left:-60px;top:-40px;width:440px;height:280px;background:repeating-linear-gradient(115deg, color-mix(in srgb, var(--cor) 7%, transparent) 0 3px, transparent 3px 24px)');
499
+ A.push(loop(fundo, [{ transform: 'translateX(0)' }, { transform: 'translateX(-53px)' }], { duration: 360, easing: 'linear' }));
500
+ const cam = peca(m, 'left:0;top:0;width:320px;height:200px');
501
+ A.push(loop(cam, [
502
+ { transform: T(0, 0, 0, 1) }, { transform: T(0, 0, 0, 1), offset: .3 },
503
+ { transform: T(0, 0, -2, 1.14), offset: .33, easing: 'ease-out' }, { transform: T(0, 0, 0, 1), offset: .45 },
504
+ { transform: T(0, 0, 0, 1), offset: .64 }, { transform: T(0, 0, 2, 1.12), offset: .67, easing: 'ease-out' },
505
+ { transform: T(0, 0, 0, 1), offset: .79 }, { transform: T(0, 0, 0, 1) }], { duration: D }));
506
+ [62, 84, 120, 140].forEach((y, i) => {
507
+ const r = peca(cam, `left:0;top:${y}px;width:${120 + i * 30}px;height:${i % 2 ? 2 : 3}px;border-radius:2px;background:${TINTA}`);
508
+ const t = .02 + i * .015;
509
+ A.push(loop(r, [
510
+ { transform: 'translateX(-260px) scaleX(.3)', opacity: 0 }, { transform: 'translateX(-260px) scaleX(.3)', opacity: 0, offset: t, easing: 'ease-out' },
511
+ { transform: 'translateX(120px) scaleX(1)', opacity: .9, offset: t + .12 }, { transform: 'translateX(380px) scaleX(.2)', opacity: 0, offset: t + .22 },
512
+ { transform: 'translateX(380px) scaleX(.2)', opacity: 0 }], { duration: D }));
513
+ });
514
+ const p = palavra(cam, 'left:0;right:0;top:56px;text-align:center;font-size:70px;font-weight:900;font-style:italic;letter-spacing:-.05em');
515
+ A.push(loop(p, [
516
+ { transform: 'translateX(-380px) skewX(-35deg)', easing: CHICOTE },
517
+ { transform: 'translateX(14px) skewX(14deg)', offset: .16, easing: 'ease-out' },
518
+ { transform: 'translateX(0) skewX(0deg)', offset: .24 },
519
+ { transform: 'translateX(0) skewX(0deg)', offset: .86, easing: 'cubic-bezier(.7,0,.9,.3)' },
520
+ { transform: 'translateX(400px) skewX(-30deg)' }], { duration: D }));
521
+ const sub = peca(cam, `left:78px;top:132px;width:164px;height:9px;border-radius:2px;background:${TINTA};transform-origin:left`);
522
+ A.push(loop(sub, [
523
+ { transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .28, easing: OVER }, { transform: 'scaleX(1.12)', offset: .33 },
524
+ { transform: 'scaleX(1)', offset: .4 }, { transform: 'scaleX(1)', offset: .86 }, { transform: 'scaleX(0)', offset: .92 }, { transform: 'scaleX(0)' }], { duration: D }));
525
+ for (let i = 0; i < 8; i++) {
526
+ const x = i < 4 ? 78 : 242, a = (i % 4) * 40 - 60 + (i < 4 ? 180 : 0), b = i < 4 ? .31 : .65;
527
+ const f = peca(cam, `left:${x}px;top:136px;width:16px;height:2.5px;border-radius:2px;background:${TINTA};transform-origin:left center;opacity:0`);
528
+ A.push(loop(f, [
529
+ { opacity: 0, transform: `rotate(${a}deg) translateX(0) scaleX(.2)` }, { opacity: 0, transform: `rotate(${a}deg) translateX(0) scaleX(.2)`, offset: b },
530
+ { opacity: 1, transform: `rotate(${a}deg) translateX(6px) scaleX(1)`, offset: b + .02, easing: 'ease-out' },
531
+ { opacity: 0, transform: `rotate(${a}deg) translateX(30px) scaleX(.3)`, offset: b + .13 }, { opacity: 0, transform: `rotate(${a}deg) translateX(30px) scaleX(.3)` }], { duration: D }));
532
+ }
533
+ const selo = palavra(cam, `left:224px;top:28px;font-size:13px;font-weight:800;letter-spacing:.06em;padding:5px 9px;border-radius:6px;background:${TINTA};color:var(--palco)`, 'NOVO');
534
+ A.push(loop(selo, [
535
+ { transform: T(0, 0, -30, 0), opacity: 0 }, { transform: T(0, 0, -30, 0), opacity: 0, offset: .63 },
536
+ { transform: T(0, 0, -14, 2.2), opacity: .2, offset: .64, easing: 'ease-in' }, { transform: T(0, 0, -12, 1), opacity: 1, offset: .69 },
537
+ { transform: T(0, 0, -12, 1), opacity: 1, offset: .86 }, { transform: T(0, 0, 0, 0), opacity: 0, offset: .92 }, { transform: T(0, 0, 0, 0), opacity: 0 }], { duration: D }));
538
+ }
539
+
540
+ /* Cinema 3D: três planos empilhados em profundidade giram no espaço, a sombra acompanha e a palavra voa do fundo. */
541
+ function cenaCinema3d(m, A) {
542
+ const D = 4200, VAI = 'cubic-bezier(.45,0,.55,1)';
543
+ const chao = peca(m, 'left:96px;top:146px;width:128px;height:20px;border-radius:50%;background:radial-gradient(closest-side, color-mix(in srgb, var(--cor) 24%, transparent), transparent)');
544
+ A.push(loop(chao, [{ transform: 'scaleX(1)', easing: VAI }, { transform: 'scaleX(.72)', offset: .5, easing: VAI }, { transform: 'scaleX(1)' }], { duration: D }));
545
+ const pilha = peca(m, 'left:95px;top:26px;width:130px;height:86px;transform-style:preserve-3d');
546
+ const planos = [0, 1, 2].map((i) => peca(pilha, `left:0;top:0;width:130px;height:86px;box-sizing:border-box;border-radius:14px;border:2px solid ${TINTA};background:${i === 2 ? 'var(--palco)' : VEU};opacity:${i === 0 ? .5 : i === 1 ? .8 : 1};transform:translateZ(${(i - 1) * 34}px)`));
547
+ peca(planos[2], `left:16px;top:18px;width:34px;height:34px;border-radius:50%;border:2.5px solid ${TINTA};box-sizing:border-box`);
548
+ peca(planos[2], `left:62px;top:24px;width:50px;height:6px;border-radius:3px;background:${TINTA}`);
549
+ peca(planos[2], `left:62px;top:38px;width:34px;height:6px;border-radius:3px;background:${TINTA};opacity:.4`);
550
+ peca(planos[2], `left:16px;top:62px;width:96px;height:6px;border-radius:3px;background:${TINTA};opacity:.2`);
551
+ A.push(loop(pilha, [
552
+ { transform: 'translateY(0) rotateX(16deg) rotateY(-38deg)', easing: VAI },
553
+ { transform: 'translateY(-8px) rotateX(6deg) rotateY(38deg)', offset: .5, easing: VAI },
554
+ { transform: 'translateY(0) rotateX(16deg) rotateY(-38deg)' }], { duration: D }));
555
+ const p = palavra(m, 'left:0;right:0;top:140px;text-align:center;font-size:30px;letter-spacing:.02em');
556
+ A.push(loop(p, [
557
+ { transform: 'translateZ(-900px)', opacity: 0, filter: 'blur(8px)' },
558
+ { transform: 'translateZ(-900px)', opacity: 0, filter: 'blur(8px)', offset: .12, easing: 'cubic-bezier(.15,.85,.3,1)' },
559
+ { transform: 'translateZ(50px)', opacity: 1, filter: 'blur(0px)', offset: .4, easing: 'ease-out' },
560
+ { transform: 'translateZ(0px)', opacity: 1, filter: 'blur(0px)', offset: .48 },
561
+ { transform: 'translateZ(0px)', opacity: 1, filter: 'blur(0px)', offset: .86, easing: 'ease-in' },
562
+ { transform: 'translateZ(380px)', opacity: 0, filter: 'blur(6px)' }], { duration: D }));
563
+ }
564
+
565
+ /* Letra que dança: cada letra chega por um caminho, uma onda passa por elas, o O gira e todas se espalham. */
566
+ function cenaCinetico(m, A) {
567
+ const D = 3400;
568
+ const de = [[0, -140, -30, 1], [0, 140, 40, 1], [0, 0, 180, 0], [-200, 0, -40, 1], [220, 0, -200, 1]];
569
+ letras(m, 'left:0;right:0;top:64px;justify-content:center;font-size:66px;font-weight:900').forEach((s, i) => {
570
+ const t0 = i * .045, onda = .38 + i * .045;
571
+ const kf = [
572
+ { transform: T(...de[i]), opacity: 0 },
573
+ { transform: T(...de[i]), opacity: 0, offset: t0, easing: OVER },
574
+ { transform: T(), opacity: 1, offset: t0 + .16 },
575
+ { transform: T(), opacity: 1, offset: onda, easing: 'ease-out' },
576
+ { transform: T(0, -22, 0, .9, 1.2), opacity: 1, offset: onda + .06, easing: 'ease-in' },
577
+ { transform: T(0, 4, 0, 1.12, .86), opacity: 1, offset: onda + .11, easing: 'ease-out' },
578
+ { transform: T(), opacity: 1, offset: onda + .15 },
579
+ ];
580
+ if (i === 4) kf.push({ transform: T(0, 0, 0, 1), opacity: 1, offset: .74, easing: OVER }, { transform: T(0, -10, 360, 1.2), opacity: 1, offset: .82 }, { transform: T(0, 0, 360, 1), opacity: 1, offset: .86 });
581
+ else kf.push({ transform: T(), opacity: 1, offset: .86 });
582
+ const r0 = i === 4 ? 360 : 0;
583
+ kf.push({ transform: T(0, 0, r0, 1), opacity: 1, offset: .88 + i * .01, easing: 'ease-in' },
584
+ { transform: T((i - 2) * 34, -150, r0 + (i - 2) * 40, .6), opacity: 0, offset: .96 + i * .008 },
585
+ { transform: T((i - 2) * 34, -150, r0 + (i - 2) * 40, .6), opacity: 0 });
586
+ A.push(loop(s, kf, { duration: D }));
587
+ });
588
+ }
589
+
590
+ /* O anti-exemplo do passo 3: colado na borda, tremendo, coisa demais na tela. */
591
+ function cenaNao(m, A) {
592
+ const p = palavra(m, 'left:8px;top:8px;font-size:34px');
593
+ const c = peca(m, `left:236px;top:118px;width:110px;height:72px;border-radius:12px;border:2.5px solid ${TINTA}`);
594
+ const b = peca(m, `left:150px;top:120px;width:24px;height:24px;border-radius:50%;background:${TINTA}`);
595
+ [[40, 150], [120, 40], [200, 72], [262, 40], [92, 108]].forEach(([x, y], i) => {
596
+ const q = peca(m, `left:${x}px;top:${y}px;width:${8 + i * 3}px;height:${8 + i * 3}px;border-radius:${i % 2 ? '50%' : '2px'};border:2px solid ${TINTA};opacity:.6`);
597
+ A.push(loop(q, [{ transform: T(0, 0, 0) }, { transform: T(i * 3 - 6, 5 - i * 2, 90 * (i + 1)) }], { duration: 400 + i * 90, direction: 'alternate' }));
598
+ });
599
+ peca(m, 'right:10px;top:10px;width:26px;height:26px;border-radius:50%;display:grid;place-items:center;background:var(--vermelho);color:#fff;font:700 14px/1 var(--f-display)', '✕');
600
+ A.push(loop(p, [{ transform: T(0, 0) }, { transform: T(3, -2, 1) }, { transform: T(-3, 2, -1) }, { transform: T(2, 3) }, { transform: T(0, 0) }], { duration: 180 }));
601
+ A.push(loop(c, [{ transform: T(0, 0, -6) }, { transform: T(0, 0, 8, 1.15) }, { transform: T(0, 0, -6) }], { duration: 500 }));
602
+ A.push(loop(b, [{ transform: T(-60, 0) }, { transform: T(60, -30) }, { transform: T(-40, 20) }, { transform: T(-60, 0) }], { duration: 700 }));
603
+ }
604
+
605
+ /* ── mais 14 motions ─────────────────────────────────────────────────────
606
+ Mesmo mundo de 320×200; ACENTO é o azul da tela, pra cada cena ter duas cores. */
607
+ const ACENTO = 'var(--ambar)';
608
+ const sempre = (o) => [{ ...o }, { ...o }];
609
+
610
+ /* Órbita: três anéis inclinados giram em volta da palavra, cada um com sua lua. */
611
+ function cenaOrbita(m, A) {
612
+ const D = 3600;
613
+ [[68, 0, ACENTO], [68, 60, TINTA], [68, -60, ACENTO]].forEach(([rx, rz, c], i) => {
614
+ const anel = peca(m, `left:65px;top:5px;width:190px;height:190px;border-radius:50%;border:1.5px solid color-mix(in srgb, ${c} 40%, transparent);transform-style:preserve-3d;transform:rotateZ(${rz}deg) rotateX(${rx}deg)`);
615
+ const lua = peca(anel, `left:88px;top:-7px;width:13px;height:13px;border-radius:50%;background:${c};box-shadow:0 0 14px ${c}`);
616
+ A.push(loop(anel, [{ transform: `rotateZ(${rz}deg) rotateX(${rx}deg) rotateZ(0deg)` }, { transform: `rotateZ(${rz}deg) rotateX(${rx}deg) rotateZ(360deg)` }], { duration: 2200 + i * 650, easing: 'linear' }));
617
+ A.push(loop(lua, [{ transform: 'scale(1)' }, { transform: 'scale(1.5)' }], { duration: 900 + i * 200, direction: 'alternate', easing: LISO }));
618
+ });
619
+ const p = palavra(m, 'left:0;right:0;top:80px;text-align:center;font-size:40px');
620
+ A.push(loop(p, [{ transform: 'scale(.5)', opacity: 0, filter: 'blur(6px)' }, { transform: 'scale(1.1)', opacity: 1, filter: 'blur(0px)', offset: .18, easing: OVER },
621
+ { transform: 'scale(1)', opacity: 1, filter: 'blur(0px)', offset: .26 }, { transform: 'scale(1)', opacity: 1, filter: 'blur(0px)', offset: .88, easing: 'ease-in' },
622
+ { transform: 'scale(.5)', opacity: 0, filter: 'blur(6px)' }], { duration: D }));
623
+ }
624
+
625
+ /* Gota: uma gota azul cai, abre três ondas na água e a palavra sobe de dentro delas. */
626
+ function cenaGota(m, A) {
627
+ const D = 3000;
628
+ const g = peca(m, `left:153px;top:-24px;width:14px;height:19px;border-radius:50% 50% 50% 50%/60% 60% 40% 40%;background:${ACENTO}`);
629
+ A.push(loop(g, [{ transform: T(0, 0), opacity: 1, easing: CAI }, { transform: T(0, 142, 0, .8, 1.3), opacity: 1, offset: .22 },
630
+ { transform: T(0, 142, 0, 2.2, 0), opacity: 0, offset: .25 }, { transform: T(0, 0), opacity: 0 }], { duration: D }));
631
+ for (let i = 0; i < 3; i++) {
632
+ const r = peca(m, `left:110px;top:128px;width:100px;height:26px;border-radius:50%;border:2px solid ${ACENTO}`);
633
+ const t = .24 + i * .06;
634
+ A.push(loop(r, [{ transform: 'scale(0)', opacity: 0 }, { transform: 'scale(0)', opacity: 0, offset: t },
635
+ { transform: 'scale(.3)', opacity: 1, offset: t + .01, easing: 'cubic-bezier(.1,.7,.3,1)' }, { transform: 'scale(2.8)', opacity: 0, offset: t + .42 },
636
+ { transform: 'scale(2.8)', opacity: 0 }], { duration: D }));
637
+ }
638
+ const p = palavra(m, 'left:0;right:0;top:66px;text-align:center;font-size:54px;transform-origin:50% 100%');
639
+ A.push(loop(p, [{ transform: 'translateY(46px) scaleY(.1)', opacity: 0, filter: 'blur(6px)' },
640
+ { transform: 'translateY(46px) scaleY(.1)', opacity: 0, filter: 'blur(6px)', offset: .27, easing: OVER },
641
+ { transform: 'translateY(0) scaleY(1)', opacity: 1, filter: 'blur(0px)', offset: .46 },
642
+ { transform: 'translateY(0) scaleY(1)', opacity: 1, filter: 'blur(0px)', offset: .86, easing: 'ease-in' },
643
+ { transform: 'translateY(30px) scaleY(.3)', opacity: 0, filter: 'blur(4px)' }], { duration: D }));
644
+ }
645
+
646
+ /* Glitch: a palavra treme, se parte em fatias coloridas e um raio de varredura corta a tela. */
647
+ function cenaGlitch(m, A) {
648
+ const D = 2200, base = 'left:0;right:0;top:62px;text-align:center;font-size:66px;font-weight:900;';
649
+ const c1 = palavra(m, base + `color:${ACENTO};opacity:.9`), c2 = palavra(m, base + 'color:var(--vermelho);opacity:.8'), p = palavra(m, base);
650
+ const pulo = (dx) => [
651
+ { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)' }, { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .08 },
652
+ { transform: T(dx, 0), clipPath: 'inset(10% 0 55% 0)', offset: .1 }, { transform: T(-dx, 2), clipPath: 'inset(52% 0 18% 0)', offset: .14 },
653
+ { transform: T(dx / 2, 0), clipPath: 'inset(30% 0 40% 0)', offset: .17 }, { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .19 },
654
+ { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .56 }, { transform: T(dx * 1.6, -2), clipPath: 'inset(28% 0 42% 0)', offset: .58 },
655
+ { transform: T(-dx, 1), clipPath: 'inset(68% 0 6% 0)', offset: .62 }, { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)', offset: .65 },
656
+ { transform: T(0, 0), clipPath: 'inset(0 0 100% 0)' }];
657
+ A.push(loop(c1, pulo(-10), { duration: D }));
658
+ A.push(loop(c2, pulo(10), { duration: D }));
659
+ A.push(loop(p, [{ transform: T(0, 0), opacity: 1 }, { transform: T(0, 0), opacity: 1, offset: .09 }, { transform: T(4, 0, 0, 1.05, .9), opacity: .5, offset: .12 },
660
+ { transform: T(-2, 0), opacity: 1, offset: .17 }, { transform: T(0, 0), opacity: 1, offset: .57 }, { transform: T(-5, 0, 0, 1.08, .92), opacity: .4, offset: .6 },
661
+ { transform: T(0, 0), opacity: 1, offset: .65 }, { transform: T(0, 0), opacity: 1 }], { duration: D }));
662
+ [[48, 5], [96, 3], [132, 8]].forEach(([y, h], i) => {
663
+ const b = peca(m, `left:0;top:${y}px;width:320px;height:${h}px;background:${i === 1 ? 'var(--vermelho)' : ACENTO}`);
664
+ const t = .1 + i * .015, t2 = .58 + i * .015;
665
+ A.push(loop(b, [{ transform: 'translateX(-60px) scaleX(.3)', opacity: 0 }, { transform: 'translateX(-60px) scaleX(.3)', opacity: 0, offset: t },
666
+ { transform: 'translateX(0) scaleX(1)', opacity: .75, offset: t + .015 }, { transform: 'translateX(60px) scaleX(.3)', opacity: 0, offset: t + .04 },
667
+ { transform: 'translateX(60px) scaleX(.3)', opacity: 0, offset: t2 }, { transform: 'translateX(0) scaleX(1)', opacity: .75, offset: t2 + .015 },
668
+ { transform: 'translateX(-60px) scaleX(.3)', opacity: 0, offset: t2 + .04 }, { transform: 'translateX(-60px) scaleX(.3)', opacity: 0 }], { duration: D }));
669
+ });
670
+ }
671
+
672
+ /* Contador: três rolos de número giram até 100%, a barra enche junto e o número dá um soco no fim. */
673
+ function cenaContador(m, A) {
674
+ const D = 3200, h = 64;
675
+ const caixa = peca(m, `left:0;right:0;top:46px;height:${h}px;display:flex;justify-content:center;align-items:center;gap:1px`);
676
+ [1, 10, 10].forEach((alvo, i) => {
677
+ const jan = el('div', { style: `height:${h}px;overflow:hidden` });
678
+ const fita = el('div', { style: `display:flex;flex-direction:column;font:900 58px/${h}px var(--f-display);letter-spacing:-.04em;color:${TINTA};font-variant-numeric:tabular-nums` }, '01234567890'.split('').map((d) => el('span', { text: d })));
679
+ jan.appendChild(fita); caixa.appendChild(jan);
680
+ A.push(loop(fita, [{ transform: 'translateY(0)' }, { transform: 'translateY(0)', offset: .06, easing: 'cubic-bezier(.6,0,.2,1)' },
681
+ { transform: `translateY(${-alvo * h}px)`, offset: .5 + i * .05 }, { transform: `translateY(${-alvo * h}px)`, offset: .9, easing: 'ease-in' },
682
+ { transform: 'translateY(0)' }], { duration: D }));
683
+ });
684
+ caixa.appendChild(el('div', { text: '%', style: `font:800 32px/1 var(--f-display);color:${ACENTO};margin-left:4px` }));
685
+ const trilho = peca(m, `left:70px;top:134px;width:180px;height:6px;border-radius:3px;background:${VEU}`);
686
+ const cheio = peca(trilho, `left:0;top:0;width:180px;height:6px;border-radius:3px;background:${ACENTO};transform-origin:left`);
687
+ A.push(loop(cheio, [{ transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .06, easing: 'cubic-bezier(.6,0,.2,1)' }, { transform: 'scaleX(1)', offset: .6 },
688
+ { transform: 'scaleX(1)', offset: .9, easing: 'ease-in' }, { transform: 'scaleX(0)' }], { duration: D }));
689
+ A.push(loop(caixa, [{ transform: 'scale(1)' }, { transform: 'scale(1)', offset: .6, easing: 'ease-out' }, { transform: 'scale(1.18)', offset: .64, easing: OVER },
690
+ { transform: 'scale(1)', offset: .74 }, { transform: 'scale(1)' }], { duration: D }));
691
+ }
692
+
693
+ /* Cubo: um cubo 3D com uma letra por face gira de quadro em quadro e dá uma cambalhota no fim. */
694
+ function cenaCubo(m, A) {
695
+ const s = 92, D = 4800;
696
+ const chao = peca(m, `left:110px;top:160px;width:100px;height:18px;border-radius:50%;background:radial-gradient(closest-side, color-mix(in srgb, var(--cor) 26%, transparent), transparent)`);
697
+ const cubo = peca(m, `left:114px;top:44px;width:${s}px;height:${s}px;transform-style:preserve-3d`);
698
+ [['rotateY(0deg)', 'P'], ['rotateY(90deg)', 'R'], ['rotateY(180deg)', 'I'], ['rotateY(-90deg)', 'M'], ['rotateX(90deg)', 'O'], ['rotateX(-90deg)', '✦']].forEach(([r, t], i) => {
699
+ const azul = i % 2 === 1;
700
+ peca(cubo, `left:0;top:0;width:${s}px;height:${s}px;box-sizing:border-box;border-radius:14px;border:2px solid ${azul ? ACENTO : TINTA};background:${azul ? ACENTO : 'var(--palco)'};color:${azul ? 'var(--sobre)' : TINTA};display:grid;place-items:center;font:900 46px/1 var(--f-display);transform:${r} translateZ(${s / 2}px);backface-visibility:hidden`, t);
701
+ });
702
+ const k = (y, x = -22) => `rotateX(${x}deg) rotateY(${y}deg)`;
703
+ A.push(loop(cubo, [{ transform: k(0), easing: OVER }, { transform: k(-90), offset: .18 }, { transform: k(-90), offset: .24, easing: OVER },
704
+ { transform: k(-180), offset: .42 }, { transform: k(-180), offset: .48, easing: OVER }, { transform: k(-270), offset: .66 },
705
+ { transform: k(-270), offset: .72, easing: 'cubic-bezier(.6,0,.3,1)' }, { transform: k(-360, 338), offset: .94 }, { transform: k(-360, 338) }], { duration: D }));
706
+ A.push(loop(chao, [{ transform: 'scale(1)' }, { transform: 'scale(1)', offset: .72 }, { transform: 'scale(.6)', offset: .83 }, { transform: 'scale(1)', offset: .94 }, { transform: 'scale(1)' }], { duration: D }));
707
+ }
708
+
709
+ /* Corte ao meio: as duas metades da palavra entram por lados opostos, se abrem e mostram a linha do meio. */
710
+ function cenaSplit(m, A) {
711
+ const D = 3000, base = 'left:0;right:0;top:56px;text-align:center;font-size:80px;font-weight:900;letter-spacing:-.05em;';
712
+ const cima = palavra(m, base + 'clip-path:inset(0 0 50% 0)'), baixo = palavra(m, base + 'clip-path:inset(50% 0 0 0)');
713
+ const meio = palavra(m, `left:0;right:0;top:91px;text-align:center;font-family:var(--f-mono);font-size:12px;font-weight:700;letter-spacing:.6em;color:${ACENTO}`, 'MOTION · 2026');
714
+ const lado = (sx, dy) => [{ transform: T(sx * 340, 0) }, { transform: T(0, 0), offset: .16, easing: MD3 }, { transform: T(0, 0), offset: .26, easing: OVER },
715
+ { transform: T(0, dy), offset: .38 }, { transform: T(0, dy), offset: .76, easing: MD3 }, { transform: T(0, 0), offset: .84, easing: CHICOTE }, { transform: T(-sx * 340, 0) }];
716
+ lado.bind(null);
717
+ A.push(loop(cima, [{ ...lado(-1, -18)[0], easing: CHICOTE }, ...lado(-1, -18).slice(1)], { duration: D }));
718
+ A.push(loop(baixo, [{ ...lado(1, 18)[0], easing: CHICOTE }, ...lado(1, 18).slice(1)], { duration: D }));
719
+ A.push(loop(meio, [{ transform: 'scaleX(0)', opacity: 0 }, { transform: 'scaleX(0)', opacity: 0, offset: .3, easing: MD3 }, { transform: 'scaleX(1)', opacity: 1, offset: .46 },
720
+ { transform: 'scaleX(1)', opacity: 1, offset: .74, easing: MD3 }, { transform: 'scaleX(0)', opacity: 0, offset: .82 }, { transform: 'scaleX(0)', opacity: 0 }], { duration: D }));
721
+ }
722
+
723
+ /* Mosaico: 40 azulejos viram em onda na diagonal, mostram a palavra e viram de novo pra ir embora. */
724
+ function cenaMosaico(m, A) {
725
+ const D = 3400, cols = 8, rows = 5, w = 30, x0 = 40, y0 = 25;
726
+ for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
727
+ const q = peca(m, `left:${x0 + c * w}px;top:${y0 + r * w}px;width:${w - 3}px;height:${w - 3}px;border-radius:6px;background:${(r + c) % 5 ? ACENTO : TINTA}`);
728
+ const d = (r + c) * .025;
729
+ A.push(loop(q, [{ transform: 'rotateY(90deg) scale(.4)', opacity: 0 }, { transform: 'rotateY(90deg) scale(.4)', opacity: 0, offset: .04 + d, easing: OVER },
730
+ { transform: 'rotateY(0deg) scale(1)', opacity: 1, offset: .14 + d }, { transform: 'rotateY(0deg) scale(1)', opacity: 1, offset: .5 + d, easing: 'ease-in' },
731
+ { transform: 'rotateY(-90deg) scale(.4)', opacity: 0, offset: .6 + d }, { transform: 'rotateY(-90deg) scale(.4)', opacity: 0 }], { duration: D }));
732
+ }
733
+ const claro = palavra(m, 'left:0;right:0;top:72px;text-align:center;font-size:56px;color:var(--sobre)');
734
+ const escuro = palavra(m, 'left:0;right:0;top:72px;text-align:center;font-size:56px');
735
+ A.push(loop(claro, [{ opacity: 0, transform: 'scale(.8)' }, { opacity: 0, transform: 'scale(.8)', offset: .3, easing: OVER }, { opacity: 1, transform: 'scale(1)', offset: .4 },
736
+ { opacity: 1, transform: 'scale(1)', offset: .58 }, { opacity: 0, transform: 'scale(1)', offset: .7 }, { opacity: 0, transform: 'scale(1)' }], { duration: D }));
737
+ A.push(loop(escuro, [{ opacity: 0, transform: 'scale(1)' }, { opacity: 0, transform: 'scale(1)', offset: .58 }, { opacity: 1, transform: 'scale(1)', offset: .7 },
738
+ { opacity: 1, transform: 'scale(1)', offset: .88, easing: 'ease-in' }, { opacity: 0, transform: 'scale(1.3)' }], { duration: D }));
739
+ }
740
+
741
+ /* Espiral: dezesseis pontos giram em espiral até o centro e explodem na palavra. */
742
+ function cenaEspiral(m, A) {
743
+ const D = 3200;
744
+ for (let i = 0; i < 16; i++) {
745
+ const a = i * 22.5, d = peca(m, `left:156px;top:96px;width:8px;height:8px;border-radius:50%;background:${i % 2 ? ACENTO : TINTA}`);
746
+ A.push(loop(d, [{ transform: `rotate(${a}deg) translateX(150px) scale(1)`, opacity: 0 },
747
+ { transform: `rotate(${a}deg) translateX(150px) scale(1)`, opacity: 1, offset: .05, easing: 'cubic-bezier(.5,0,.8,.2)' },
748
+ { transform: `rotate(${a + 540}deg) translateX(0px) scale(.4)`, opacity: 1, offset: .4 },
749
+ { transform: `rotate(${a + 540}deg) translateX(0px) scale(0)`, opacity: 0, offset: .42 }, { transform: `rotate(${a + 540}deg) translateX(0px) scale(0)`, opacity: 0 }], { duration: D }));
750
+ }
751
+ const anel = peca(m, `left:130px;top:70px;width:60px;height:60px;border-radius:50%;border:3px solid ${ACENTO}`);
752
+ A.push(loop(anel, [{ transform: 'scale(0)', opacity: 0 }, { transform: 'scale(0)', opacity: 0, offset: .4 }, { transform: 'scale(.5)', opacity: 1, offset: .41, easing: 'cubic-bezier(.1,.8,.3,1)' },
753
+ { transform: 'scale(4)', opacity: 0, offset: .62 }, { transform: 'scale(4)', opacity: 0 }], { duration: D }));
754
+ const p = palavra(m, 'left:0;right:0;top:76px;text-align:center;font-size:50px;font-weight:900');
755
+ A.push(loop(p, [{ transform: T(0, 0, -90, 0) }, { transform: T(0, 0, -90, 0), offset: .4, easing: OVER }, { transform: T(0, 0, 6, 1.25), offset: .5 },
756
+ { transform: T(0, 0, 0, 1), offset: .58 }, { transform: T(0, 0, 0, 1), offset: .86, easing: 'cubic-bezier(.6,0,1,.5)' }, { transform: T(0, 0, 200, 0) }], { duration: D }));
757
+ }
758
+
759
+ /* Caça-níquel: cada letra roda como num rolo de cassino, borrando, e trava uma de cada vez. */
760
+ function cenaSlot(m, A) {
761
+ const D = 3400, h = 70, mix = 'KZAXQVTEWYUBGH';
762
+ const caixa = peca(m, `left:0;right:0;top:56px;height:${h}px;display:flex;justify-content:center;gap:5px`);
763
+ 'PRIMO'.split('').forEach((ch, i) => {
764
+ const seq = mix.slice(i, i + 7).split('').concat(ch), fim = -(seq.length - 1) * h;
765
+ const jan = el('div', { style: `height:${h}px;width:48px;overflow:hidden;border-radius:12px;background:${VEU}` });
766
+ const fita = el('div', { style: `display:flex;flex-direction:column;align-items:center;font:900 54px/${h}px var(--f-display);color:${TINTA}` }, seq.map((c) => el('span', { text: c })));
767
+ jan.appendChild(fita); caixa.appendChild(jan);
768
+ A.push(loop(fita, [{ transform: 'translateY(0)', filter: 'blur(0px)' }, { transform: 'translateY(0)', filter: 'blur(0px)', offset: .04, easing: 'ease-in' },
769
+ { transform: `translateY(${fim * .6}px)`, filter: 'blur(4px)', offset: .2 + i * .05 }, { transform: `translateY(${fim - 16}px)`, filter: 'blur(0px)', offset: .3 + i * .07, easing: 'ease-out' },
770
+ { transform: `translateY(${fim}px)`, filter: 'blur(0px)', offset: .36 + i * .07 }, { transform: `translateY(${fim}px)`, filter: 'blur(0px)', offset: .92 },
771
+ { transform: 'translateY(0)', filter: 'blur(3px)' }], { duration: D }));
772
+ A.push(loop(jan, [{ boxShadow: '0 0 0 0 transparent' }, { boxShadow: '0 0 0 0 transparent', offset: .35 + i * .07 },
773
+ { boxShadow: `0 0 0 3px ${ACENTO}`, offset: .37 + i * .07 }, { boxShadow: '0 0 0 0 transparent', offset: .5 + i * .07 }, { boxShadow: '0 0 0 0 transparent' }], { duration: D }));
774
+ });
775
+ }
776
+
777
+ /* Túnel: quadrados saem do centro girando sem parar, e a palavra pulsa no meio como batida. */
778
+ function cenaTunel(m, A) {
779
+ for (let i = 0; i < 6; i++) {
780
+ const q = peca(m, `left:110px;top:50px;width:100px;height:100px;border-radius:16px;border:2.5px solid ${i % 2 ? ACENTO : TINTA}`);
781
+ A.push(loop(q, [{ transform: 'scale(0) rotate(0deg)', opacity: 0 }, { transform: 'scale(.7) rotate(40deg)', opacity: 1, offset: .3 }, { transform: 'scale(4.2) rotate(130deg)', opacity: 0 }],
782
+ { duration: 2400, delay: -i * 400, easing: 'cubic-bezier(.5,0,.9,.5)' }));
783
+ }
784
+ const fundo = peca(m, `left:122px;top:80px;width:76px;height:40px;border-radius:10px;background:var(--palco)`);
785
+ const p = palavra(m, 'left:0;right:0;top:82px;text-align:center;font-size:36px;font-weight:900');
786
+ [fundo, p].forEach((e) => A.push(loop(e, [{ transform: 'scale(1)', easing: 'ease-out' }, { transform: 'scale(1.16)', offset: .12, easing: 'ease-in-out' }, { transform: 'scale(1)', offset: .5 }, { transform: 'scale(1)' }], { duration: 800 })));
787
+ }
788
+
789
+ /* Equalizador: dezoito barras dançam em ritmos diferentes e a palavra quica na batida. */
790
+ function cenaEqualizador(m, A) {
791
+ for (let i = 0; i < 18; i++) {
792
+ const b = peca(m, `left:${35 + i * 14}px;top:76px;width:9px;height:104px;border-radius:5px;background:${i % 3 ? TINTA : ACENTO};transform-origin:bottom`);
793
+ const hs = [.2, .9, .4, 1, .3].map((v, k) => ((v + i * .37 + k * .21) % 1) * .85 + .15);
794
+ A.push(loop(b, [...hs, hs[0]].map((v) => ({ transform: `scaleY(${v})` })), { duration: 900 + (i % 4) * 140, easing: 'ease-in-out' }));
795
+ }
796
+ const p = palavra(m, 'left:0;right:0;top:18px;text-align:center;font-size:42px;font-weight:900');
797
+ A.push(loop(p, [{ transform: T(0, 0, 0, 1), easing: 'ease-out' }, { transform: T(0, -8, -3, 1.08), offset: .2, easing: 'ease-in' }, { transform: T(0, 0, 0, 1), offset: .5 },
798
+ { transform: T(0, -8, 3, 1.08), offset: .7, easing: 'ease-in' }, { transform: T(0, 0, 0, 1) }], { duration: 900 }));
799
+ }
800
+
801
+ /* Cartas: um baralho cai na mesa, abre em leque, junta de novo e a palavra aparece embaixo. */
802
+ function cenaCartas(m, A) {
803
+ const D = 3600;
804
+ for (let i = 0; i < 5; i++) {
805
+ const topo = i === 4, ang = (i - 2) * 16;
806
+ const c = peca(m, `left:125px;top:22px;width:70px;height:100px;box-sizing:border-box;border-radius:11px;border:2px solid ${topo ? ACENTO : TINTA};background:${topo ? ACENTO : 'var(--palco)'};transform-origin:50% 140%;display:grid;place-items:center;font:900 30px/1 var(--f-display);color:var(--sobre)`, topo ? '✦' : '');
807
+ A.push(loop(c, [{ transform: T(0, -150, -20, 1), opacity: 0 }, { transform: T(0, -150, -20, 1), opacity: 0, offset: i * .02, easing: OVER },
808
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .12 + i * .02 }, { transform: T(0, 0, 0, 1), opacity: 1, offset: .2, easing: OVER },
809
+ { transform: T((i - 2) * 24, Math.abs(i - 2) * 5, ang, 1), opacity: 1, offset: .36 }, { transform: T((i - 2) * 24, Math.abs(i - 2) * 5, ang, 1), opacity: 1, offset: .6, easing: CHICOTE },
810
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .7 }, { transform: T(0, 0, 0, 1), opacity: 1, offset: .88 + i * .015, easing: 'ease-in' },
811
+ { transform: T(0, -170, 30, 1), opacity: 0, offset: .97 + i * .005 }, { transform: T(0, -170, 30, 1), opacity: 0 }], { duration: D }));
812
+ }
813
+ const p = palavra(m, 'left:0;right:0;top:146px;text-align:center;font-size:30px');
814
+ A.push(loop(p, [{ opacity: 0, transform: 'translateY(14px)' }, { opacity: 0, transform: 'translateY(14px)', offset: .68, easing: MD3 }, { opacity: 1, transform: 'translateY(0)', offset: .78 },
815
+ { opacity: 1, transform: 'translateY(0)', offset: .9 }, { opacity: 0, transform: 'translateY(0)' }], { duration: D }));
816
+ }
817
+
818
+ /* Traço: uma linha azul se desenha pela tela com uma bolinha na ponta, e a palavra se revela atrás dela. */
819
+ function cenaTraco(m, A) {
820
+ const D = 3400, d = 'M18 150 C 70 10, 130 10, 160 100 S 250 190, 302 40';
821
+ const NS = ['http:', '', 'www.w3.org', '2000', 'svg'].join('/'), svg = document.createElementNS(NS, 'svg');
822
+ svg.setAttribute('viewBox', '0 0 320 200');
823
+ svg.style.cssText = 'position:absolute;left:0;top:0;width:320px;height:200px;overflow:visible';
824
+ const linha = (w, c, op) => { const p = document.createElementNS(NS, 'path'); Object.entries({ d, fill: 'none', stroke: c, 'stroke-width': w, 'stroke-linecap': 'round', pathLength: 100, 'stroke-dasharray': 100, 'stroke-dashoffset': 100, opacity: op }).forEach(([k, v]) => p.setAttribute(k, v)); svg.appendChild(p); return p; };
825
+ const fina = linha(2, 'var(--cor)', .25), grossa = linha(7, 'var(--ambar)', 1);
826
+ m.appendChild(svg);
827
+ const desenha = (atraso) => [{ strokeDashoffset: 100 }, { strokeDashoffset: 100, offset: atraso, easing: 'cubic-bezier(.6,0,.3,1)' }, { strokeDashoffset: 0, offset: atraso + .4 },
828
+ { strokeDashoffset: 0, offset: .82, easing: 'cubic-bezier(.6,0,.3,1)' }, { strokeDashoffset: -100, offset: .98 }, { strokeDashoffset: -100 }];
829
+ A.push(loop(grossa, desenha(.04), { duration: D }));
830
+ A.push(loop(fina, desenha(.1), { duration: D }));
831
+ const ponta = peca(m, `left:-8px;top:-8px;width:16px;height:16px;border-radius:50%;background:var(--palco);border:3px solid ${ACENTO};box-sizing:border-box;offset-path:path('${d}');offset-rotate:0deg`);
832
+ A.push(loop(ponta, [{ offsetDistance: '0%', opacity: 0 }, { offsetDistance: '0%', opacity: 1, offset: .04, easing: 'cubic-bezier(.6,0,.3,1)' }, { offsetDistance: '100%', opacity: 1, offset: .44 },
833
+ { offsetDistance: '100%', opacity: 0, offset: .5 }, { offsetDistance: '100%', opacity: 0 }], { duration: D }));
834
+ const p = palavra(m, 'left:0;right:0;top:78px;text-align:center;font-family:var(--f-serifa);font-style:italic;font-weight:400;font-size:58px;letter-spacing:-.01em', 'Primo');
835
+ A.push(loop(p, [{ clipPath: 'inset(0 100% 0 0)' }, { clipPath: 'inset(0 100% 0 0)', offset: .14, easing: 'cubic-bezier(.6,0,.3,1)' }, { clipPath: 'inset(0 0% 0 0)', offset: .46 },
836
+ { clipPath: 'inset(0 0% 0 0)', offset: .84, easing: MD3 }, { clipPath: 'inset(0 0 0 100%)', offset: .96 }, { clipPath: 'inset(0 0 0 100%)' }], { duration: D }));
837
+ }
838
+
839
+ /* Estilhaço: um impacto quebra a palavra, as letras e os cacos voam e tudo se junta de volta no lugar. */
840
+ function cenaEstilhaco(m, A) {
841
+ const D = 3200, dx = [-130, -60, 10, 80, 140], dy = [-80, 70, -95, 85, -60], rr = [-220, 170, -130, 250, -190];
842
+ letras(m, 'left:0;right:0;top:64px;justify-content:center;font-size:66px;font-weight:900').forEach((s, i) => {
843
+ A.push(loop(s, [{ transform: T(), opacity: 1 }, { transform: T(), opacity: 1, offset: .26 }, { transform: T(-3, 2), opacity: 1, offset: .28 },
844
+ { transform: T(dx[i], dy[i], rr[i], .5), opacity: 0, offset: .46, easing: 'cubic-bezier(.1,.8,.3,1)' }, { transform: T(dx[i], dy[i], rr[i], .5), opacity: 0, offset: .56, easing: OVER },
845
+ { transform: T(), opacity: 1, offset: .76 + i * .02 }, { transform: T(), opacity: 1 }].map((k, j) => (j === 3 ? { ...k, easing: undefined } : k)).map((k) => { if (k.easing === undefined) delete k.easing; return k; }), { duration: D }));
846
+ });
847
+ const flash = peca(m, `left:110px;top:50px;width:100px;height:100px;border-radius:50%;background:radial-gradient(closest-side, ${ACENTO}, transparent)`);
848
+ A.push(loop(flash, [{ transform: 'scale(0)', opacity: 0 }, { transform: 'scale(0)', opacity: 0, offset: .27 }, { transform: 'scale(1)', opacity: .9, offset: .29 },
849
+ { transform: 'scale(2.6)', opacity: 0, offset: .42 }, { transform: 'scale(2.6)', opacity: 0 }], { duration: D }));
850
+ for (let i = 0; i < 12; i++) {
851
+ const a = i * 30 + 10, r = 90 + (i % 3) * 30;
852
+ const caco = peca(m, `left:154px;top:94px;width:12px;height:12px;background:${i % 2 ? ACENTO : TINTA};clip-path:polygon(50% 0,100% 100%,0 70%)`);
853
+ A.push(loop(caco, [{ transform: `rotate(${a}deg) translateX(0) rotate(0deg) scale(0)`, opacity: 0 }, { transform: `rotate(${a}deg) translateX(0) rotate(0deg) scale(0)`, opacity: 0, offset: .28 },
854
+ { transform: `rotate(${a}deg) translateX(10px) rotate(0deg) scale(1)`, opacity: 1, offset: .29, easing: 'cubic-bezier(.1,.8,.3,1)' },
855
+ { transform: `rotate(${a}deg) translateX(${r}px) rotate(${i * 90}deg) scale(.5)`, opacity: 0, offset: .55 }, { transform: `rotate(${a}deg) translateX(${r}px) rotate(${i * 90}deg) scale(.5)`, opacity: 0 }], { duration: D }));
856
+ }
857
+ }
858
+
859
+ ESTILOS.push(
860
+ { id: 'cubo', nome: 'Cubo', frase: '', ritmo: 'medio', cameras: ['orbita', 'push'], trocas: ['giro', 'zoom'], tipografias: ['pesada', 'poster'], overshoot: .2 },
861
+ { id: 'glitch', nome: 'Glitch', frase: '', ritmo: 'rapido', cameras: ['chicote', 'pulso'], trocas: ['tremor', 'corte'], tipografias: ['tecnica', 'pesada'], overshoot: .05 },
862
+ { id: 'espiral', nome: 'Espiral', frase: '', ritmo: 'rapido', cameras: ['orbita', 'pulso'], trocas: ['zoom', 'encolhe'], tipografias: ['pesada', 'arredondada'], overshoot: .2 },
863
+ { id: 'slot', nome: 'Caça-níquel', frase: '', ritmo: 'rapido', cameras: ['pulso', 'trilho'], trocas: ['corte', 'persiana'], tipografias: ['pesada', 'poster'], overshoot: .1 },
864
+ { id: 'tunel', nome: 'Túnel', frase: '', ritmo: 'rapido', cameras: ['push', 'pulso'], trocas: ['zoom', 'giro'], tipografias: ['geometrica', 'poster'], overshoot: .1 },
865
+ { id: 'mosaico', nome: 'Mosaico', frase: '', ritmo: 'medio', cameras: ['push', 'deriva'], trocas: ['persiana', 'abre'], tipografias: ['geometrica', 'pesada'], overshoot: .15 },
866
+ { id: 'estilhaco', nome: 'Estilhaço', frase: '', ritmo: 'rapido', cameras: ['chicote', 'pulso'], trocas: ['zoom', 'tremor'], tipografias: ['pesada', 'poster'], overshoot: .22 },
867
+ { id: 'contador', nome: 'Contador', frase: '', ritmo: 'medio', cameras: ['push', 'trilho'], trocas: ['empurra', 'corte'], tipografias: ['tecnica', 'geometrica'], overshoot: .06 },
868
+ { id: 'orbita', nome: 'Órbita', frase: '', ritmo: 'medio', cameras: ['orbita', 'cinema'], trocas: ['giro', 'dissolve'], tipografias: ['geometrica'], overshoot: .08 },
869
+ { id: 'equalizador', nome: 'Equalizador', frase: '', ritmo: 'rapido', cameras: ['pulso', 'chicote'], trocas: ['corte', 'tremor'], tipografias: ['pesada', 'poster'], overshoot: .12 },
870
+ { id: 'cartas', nome: 'Cartas', frase: '', ritmo: 'medio', cameras: ['cinema', 'deriva'], trocas: ['abre', 'giro'], tipografias: ['editorial', 'arredondada'], overshoot: .18 },
871
+ { id: 'gota', nome: 'Gota', frase: '', ritmo: 'lento', cameras: ['deriva', 'cinema'], trocas: ['desfoque', 'dissolve'], tipografias: ['serifa_fina', 'editorial'], overshoot: .12 },
872
+ { id: 'split', nome: 'Corte ao meio', frase: '', ritmo: 'rapido', cameras: ['trilho', 'chicote'], trocas: ['persiana', 'empurra'], tipografias: ['pesada', 'tecnica'], overshoot: .04 },
873
+ { id: 'traco', nome: 'Traço', frase: '', ritmo: 'medio', cameras: ['trilho', 'deriva'], trocas: ['abre', 'cortina'], tipografias: ['manuscrita', 'editorial'], overshoot: .08 },
874
+ );
875
+ /* a ordem do carrossel: os mais fortes primeiro, alternando energia */
876
+ const ORDEM = ['brincalhao', 'cubo', 'glitch', 'cinetico', 'espiral', 'energico', 'slot', 'cinema3d', 'tunel', 'mosaico', 'premium', 'estilhaco',
877
+ 'corporativo', 'contador', 'orbita', 'equalizador', 'cartas', 'gota', 'split', 'traco'];
878
+ const NOVAS_CENAS = { orbita: cenaOrbita, gota: cenaGota, glitch: cenaGlitch, contador: cenaContador, cubo: cenaCubo, split: cenaSplit, mosaico: cenaMosaico,
879
+ espiral: cenaEspiral, slot: cenaSlot, tunel: cenaTunel, equalizador: cenaEqualizador, cartas: cenaCartas, traco: cenaTraco, estilhaco: cenaEstilhaco };
880
+
881
+ /* ── as prévias do catálogo ───────────────────────────────────────────── */
882
+ function quadrosAB(m) {
883
+ const base = 'left:50px;top:35px;width:220px;height:130px;box-sizing:border-box;border-radius:14px;display:grid;place-items:center;font:800 54px/1 var(--f-display);letter-spacing:-.04em;';
884
+ return [peca(m, base + `background:${VEU};color:${TINTA};border:2px solid ${TINTA}`, 'A'), peca(m, base + `background:${TINTA};color:var(--palco)`, 'B')];
885
+ }
886
+ const fixo = (o) => [{ ...o, offset: .3 }, { ...o, offset: .7 }];
887
+ const TRANSICOES_KF = {
888
+ encolhe: [[{ transform: 'scale(1)', offset: .3, easing: 'cubic-bezier(.6,0,.9,.4)' }, { transform: 'scale(0)', offset: .5 }], [{ transform: 'scale(0)', offset: .48, easing: OVER }, { transform: 'scale(1)', offset: .7 }]],
889
+ empurra: [[{ transform: 'translateX(0)', offset: .3, easing: MD3 }, { transform: 'translateX(-300px)', offset: .7 }], [{ transform: 'translateX(300px)', offset: .3, easing: MD3 }, { transform: 'translateX(0)', offset: .7 }]],
890
+ zoom: [[{ transform: 'scale(1)', opacity: 1, offset: .3, easing: 'ease-in' }, { transform: 'scale(3)', opacity: 0, offset: .52 }], [{ transform: 'scale(.3)', opacity: 0, offset: .46, easing: 'ease-out' }, { transform: 'scale(1)', opacity: 1, offset: .7 }]],
891
+ chicote: [[{ transform: 'translateX(0) skewX(0deg)', filter: 'blur(0px)', offset: .3, easing: CHICOTE }, { transform: 'translateX(-340px) skewX(-25deg)', filter: 'blur(6px)', offset: .48 }], [{ transform: 'translateX(340px) skewX(-25deg)', filter: 'blur(6px)', offset: .45, easing: 'cubic-bezier(.1,.8,.2,1)' }, { transform: 'translateX(0) skewX(0deg)', filter: 'blur(0px)', offset: .68 }]],
892
+ desfoque: [[{ filter: 'blur(0px)', opacity: 1, offset: .3, easing: LISO }, { filter: 'blur(16px)', opacity: 0, offset: .58 }], [{ filter: 'blur(16px)', opacity: 0, offset: .42, easing: LISO }, { filter: 'blur(0px)', opacity: 1, offset: .7 }]],
893
+ cortina: [fixo({ transform: 'translateY(0)' }), [{ transform: 'translateY(-230px)', offset: .3, easing: 'cubic-bezier(.3,1.25,.6,1)' }, { transform: 'translateY(0)', offset: .66 }]],
894
+ giro: [[{ transform: 'rotateY(0deg)', offset: .3, easing: 'ease-in' }, { transform: 'rotateY(90deg)', offset: .5 }], [{ transform: 'rotateY(-90deg)', offset: .5, easing: 'ease-out' }, { transform: 'rotateY(0deg)', offset: .7 }]],
895
+ tremor: [[{ transform: 'translate(0,0)', opacity: 1, offset: .3 }, { transform: 'translate(-7px,3px)', opacity: 1, offset: .34 }, { transform: 'translate(7px,-4px)', opacity: 1, offset: .38 }, { transform: 'translate(-6px,-3px)', opacity: 1, offset: .42 }, { transform: 'translate(6px,4px)', opacity: 1, offset: .46 }, { transform: 'translate(0,0)', opacity: 1, offset: .499 }, { transform: 'translate(0,0)', opacity: 0, offset: .5 }], [{ opacity: 0, offset: .499 }, { opacity: 1, offset: .5 }]],
896
+ abre: [fixo({ opacity: 1 }), [{ clipPath: 'inset(0 50% 0 50% round 14px)', offset: .3, easing: MD3 }, { clipPath: 'inset(0 0% 0 0% round 14px)', offset: .68 }]],
897
+ dissolve: [[{ opacity: 1, offset: .3, easing: LISO }, { opacity: 0, offset: .7 }], [{ opacity: 0, offset: .3, easing: LISO }, { opacity: 1, offset: .7 }]],
898
+ corte: [[{ opacity: 1, offset: .499 }, { opacity: 0, offset: .5 }], [{ opacity: 0, offset: .499 }, { opacity: 1, offset: .5 }]],
899
+ };
900
+ const completar = (kfs) => [{ ...kfs[0], offset: 0, easing: 'linear' }, ...kfs, { ...kfs[kfs.length - 1], offset: 1, easing: 'linear' }];
901
+
902
+ function previaTransicao(m, id, A) {
903
+ const [a, b] = quadrosAB(m);
904
+ const o = { duration: 1500, direction: 'alternate' };
905
+ if (id === 'persiana') {
906
+ b.style.background = 'transparent';
907
+ const letra = b.textContent; b.textContent = '';
908
+ for (let i = 0; i < 5; i++) {
909
+ const s = peca(b, `left:${i * 44}px;top:0;width:44.5px;height:130px;background:${TINTA};transform-origin:left;${i === 0 ? 'border-radius:14px 0 0 14px;' : ''}${i === 4 ? 'border-radius:0 14px 14px 0;' : ''}`);
910
+ A.push(loop(s, completar([{ transform: 'scaleX(0)', offset: .3 + i * .05, easing: MD3 }, { transform: 'scaleX(1)', offset: .45 + i * .05 }]), o));
911
+ }
912
+ const l = peca(b, 'left:0;top:0;width:220px;height:130px;display:grid;place-items:center;color:var(--palco)', letra);
913
+ A.push(loop(l, completar([{ opacity: 0, offset: .6 }, { opacity: 1, offset: .7 }]), o));
914
+ return;
915
+ }
916
+ const [ka, kb] = TRANSICOES_KF[id];
917
+ A.push(loop(a, completar(ka), o));
918
+ A.push(loop(b, completar(kb), o));
919
+ }
920
+
921
+ function previaCamera(m, id, A) {
922
+ const cam = peca(m, 'left:0;top:0;width:320px;height:200px;transform-style:preserve-3d');
923
+ const fundo = peca(cam, 'left:-60px;top:-40px;width:440px;height:280px;background-image:radial-gradient(color-mix(in srgb, var(--cor) 24%, transparent) 1.4px, transparent 1.7px);background-size:18px 18px');
924
+ const card = peca(cam, `left:96px;top:40px;width:128px;height:84px;box-sizing:border-box;border-radius:14px;background:${VEU};border:2px solid ${TINTA}`);
925
+ peca(card, `left:14px;top:16px;width:26px;height:26px;box-sizing:border-box;border-radius:50%;border:2.5px solid ${TINTA}`);
926
+ peca(card, `left:50px;top:20px;width:52px;height:6px;border-radius:3px;background:${TINTA}`);
927
+ const p = palavra(cam, 'left:0;right:0;top:138px;text-align:center;font-size:28px');
928
+ if (id === 'orbita') {
929
+ fundo.style.transform = 'translateZ(-80px)'; p.style.transform = 'translateZ(50px)';
930
+ A.push(loop(cam, [{ transform: 'rotateY(-30deg) rotateX(8deg)' }, { transform: 'rotateY(30deg) rotateX(8deg)' }], { duration: 3000, direction: 'alternate', easing: LISO }));
931
+ return;
932
+ }
933
+ const M = {
934
+ deriva: (f) => [[{ transform: `translate(${-14 * f}px,${-6 * f}px)` }, { transform: `translate(${14 * f}px,${6 * f}px)` }], { duration: 3200, direction: 'alternate', easing: LISO }],
935
+ pulso: (f) => [[{ transform: 'scale(1)', easing: 'ease-out' }, { transform: `scale(${1 + .14 * f})`, offset: .08, easing: 'ease-in-out' }, { transform: 'scale(1)', offset: .34 }, { transform: 'scale(1)' }], { duration: 700 }],
936
+ trilho: (f) => [[{ transform: `translateX(${50 * f}px)` }, { transform: `translateX(${-50 * f}px)` }], { duration: 2600, direction: 'alternate', easing: LISO }],
937
+ push: (f) => [[{ transform: 'scale(1)' }, { transform: `scale(${1 + .4 * f})` }], { duration: 2600, direction: 'alternate', easing: LISO }],
938
+ chicote: (f) => [[{ transform: 'translateX(0)', filter: 'blur(0px)' }, { transform: 'translateX(0)', filter: 'blur(0px)', offset: .4, easing: CHICOTE }, { transform: `translateX(${-90 * f}px)`, filter: `blur(${3 * f}px)`, offset: .52 }, { transform: `translateX(${-90 * f}px)`, filter: 'blur(0px)', offset: .58 }, { transform: `translateX(${-90 * f}px)`, filter: 'blur(0px)' }], { duration: 1800, direction: 'alternate' }],
939
+ cinema: (f) => [[{ transform: `translateY(${10 * f}px) scale(${1 + .04 * f}) rotate(-1deg)` }, { transform: `translateY(${-10 * f}px) scale(${1 + .16 * f}) rotate(1deg)` }], { duration: 3800, direction: 'alternate', easing: LISO }],
940
+ };
941
+ [[fundo, .35], [card, .7], [p, 1]].forEach(([e, f]) => { const [kf, o] = M[id](f); A.push(loop(e, kf, o)); });
942
+ }
943
+
944
+ function previaTipografia(m, id, A) {
945
+ const D = 2600, centro = 'left:0;right:0;text-align:center;';
946
+ const sai = (e, extra = {}) => [{ opacity: 1, ...extra, offset: .86, easing: 'ease-in' }, { opacity: 0, ...extra }];
947
+ if (id === 'editorial') {
948
+ const a = palavra(m, 'left:52px;top:76px;font-size:40px;font-weight:600;letter-spacing:-.03em', 'Primo');
949
+ const b = palavra(m, 'left:164px;top:70px;font-size:46px;font-family:var(--f-serifa);font-style:italic;font-weight:400;letter-spacing:-.01em', 'motion');
950
+ [a, b].forEach((e, i) => A.push(loop(e, [
951
+ { opacity: 0, transform: 'translateY(24px)' }, { opacity: 0, transform: 'translateY(24px)', offset: .08 + i * .12, easing: MD3 },
952
+ { opacity: 1, transform: 'translateY(0)', offset: .32 + i * .12 }, ...sai(e, { transform: 'translateY(0)' })], { duration: D })));
953
+ } else if (id === 'poster') {
954
+ const e = palavra(m, centro + 'top:50px;font-family:Impact,Haettenschweiler,"Arial Narrow Bold",var(--f-display);font-weight:400;font-size:90px;letter-spacing:0', 'PRIMO');
955
+ A.push(loop(e, [
956
+ { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(30px)' }, { clipPath: 'inset(100% 0 0 0)', transform: 'translateY(30px)', offset: .08, easing: MD3 },
957
+ { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)', offset: .34 }, { clipPath: 'inset(0 0 0 0)', transform: 'translateY(0)', offset: .84, easing: MD3 },
958
+ { clipPath: 'inset(0 0 100% 0)', transform: 'translateY(-30px)', offset: .96 }, { clipPath: 'inset(0 0 100% 0)', transform: 'translateY(-30px)' }], { duration: D }));
959
+ } else if (id === 'tecnica') {
960
+ const ls = letras(m, 'left:0;right:0;top:84px;justify-content:center;font-family:var(--f-mono);font-weight:500;font-size:26px;letter-spacing:0', 'primo.run()');
961
+ ls.forEach((s, i) => A.push(loop(s, [{ opacity: 0 }, { opacity: 0, offset: .06 + i * .04 }, { opacity: 1, offset: .061 + i * .04 }, ...sai(s)], { duration: D })));
962
+ const cur = peca(m, `left:236px;top:82px;width:12px;height:30px;background:${TINTA}`);
963
+ A.push(loop(cur, [{ opacity: 1 }, { opacity: 1, offset: .5 }, { opacity: 0, offset: .51 }, { opacity: 0 }], { duration: 700 }));
964
+ } else if (id === 'manuscrita') {
965
+ const e = palavra(m, centro + 'top:56px;font-family:"Snell Roundhand","Segoe Script","Brush Script MT",cursive;font-weight:400;font-size:70px;letter-spacing:0', 'Primo');
966
+ A.push(loop(e, [
967
+ { clipPath: 'inset(0 100% 0 0)', opacity: 1 }, { clipPath: 'inset(0 100% 0 0)', opacity: 1, offset: .08, easing: 'cubic-bezier(.45,0,.55,1)' },
968
+ { clipPath: 'inset(0 0% 0 0)', opacity: 1, offset: .58 }, ...sai(e, { clipPath: 'inset(0 0% 0 0)' })], { duration: D }));
969
+ } else if (id === 'pesada') {
970
+ const e = palavra(m, centro + 'top:58px;font-size:80px;font-weight:900;letter-spacing:-.05em');
971
+ A.push(loop(e, [
972
+ { transform: 'scale(2.6)', opacity: 0 }, { transform: 'scale(2.6)', opacity: 0, offset: .08, easing: 'cubic-bezier(.6,0,1,.6)' },
973
+ { transform: 'scale(.92)', opacity: 1, offset: .2, easing: 'ease-out' }, { transform: 'scale(1)', opacity: 1, offset: .28 }, ...sai(e, { transform: 'scale(1)' })], { duration: D }));
974
+ A.push(loop(m, [{ transform: T() }, { transform: T(), offset: .2 }, { transform: T(-5, 3) }, { transform: T(5, -3) }, { transform: T(-3, -2) }, { transform: T() }].map((k, i) => (i > 1 && i < 5 ? { ...k, offset: .2 + (i - 1) * .025 } : i === 5 ? { ...k, offset: .3 } : k)).concat([{ transform: T() }]), { duration: D }));
975
+ } else if (id === 'serifa_fina') {
976
+ const e = palavra(m, centro + 'top:60px;font-family:var(--f-serifa);font-weight:300;font-size:64px;letter-spacing:.02em', 'Primo');
977
+ A.push(loop(e, [
978
+ { opacity: 0, letterSpacing: '.45em' }, { opacity: 0, letterSpacing: '.45em', offset: .06, easing: LISO },
979
+ { opacity: 1, letterSpacing: '.02em', offset: .5 }, ...sai(e, { letterSpacing: '.02em' })], { duration: D }));
980
+ } else if (id === 'arredondada') {
981
+ letras(m, 'left:0;right:0;top:66px;justify-content:center;font-family:ui-rounded,"SF Pro Rounded","Arial Rounded MT Bold",var(--f-display);font-weight:700;font-size:62px;letter-spacing:-.02em', 'Primo').forEach((s, i) => A.push(loop(s, [
982
+ { transform: T(0, 60, 0, .3), opacity: 0 }, { transform: T(0, 60, 0, .3), opacity: 0, offset: .06 + i * .05, easing: OVER },
983
+ { transform: T(0, 0, 0, 1), opacity: 1, offset: .26 + i * .05 }, ...sai(s, { transform: T() })], { duration: D })));
984
+ } else if (id === 'geometrica') {
985
+ const e = palavra(m, 'left:58px;top:74px;font-family:"Avenir Next",Futura,"Century Gothic",var(--f-display);font-weight:500;font-size:36px;letter-spacing:.18em', 'PRIMO');
986
+ const barra = peca(m, `left:60px;top:124px;width:190px;height:4px;background:${TINTA};transform-origin:left`);
987
+ A.push(loop(e, [
988
+ { opacity: 0, transform: 'translateX(-60px)' }, { opacity: 0, transform: 'translateX(-60px)', offset: .06, easing: MD3 },
989
+ { opacity: 1, transform: 'translateX(0)', offset: .34 }, ...sai(e, { transform: 'translateX(0)' })], { duration: D }));
990
+ A.push(loop(barra, [{ transform: 'scaleX(0)' }, { transform: 'scaleX(0)', offset: .24, easing: MD3 }, { transform: 'scaleX(1)', offset: .48 }, ...sai(barra, { transform: 'scaleX(1)' })], { duration: D }));
991
+ }
992
+ }
993
+
994
+ function montarPrevia(grupo, id, palco) {
995
+ const m = mundo(palco), A = [];
996
+ if (parar) { palavra(m, 'left:0;right:0;top:80px;text-align:center;font-size:36px', id === 'poster' ? 'PRIMO' : 'Primo'); return A; }
997
+ if (grupo === 'trocas') previaTransicao(m, id, A);
998
+ else if (grupo === 'cameras') previaCamera(m, id, A);
999
+ else previaTipografia(m, id, A);
1000
+ return A;
1001
+ }
1002
+
1003
+ const cor = (id) => `var(--c-${id})`;
1004
+
1005
+ /* ── passo 1 ────────────────────────────────────────────────────────────── */
1006
+ function passo1() {
1007
+ const grade = $('#grade');
1008
+ let coluna = null;
1009
+ const conhecidos = new Set(((window.GOSTO && window.GOSTO.ESTILOS) || ESTILOS).map((x) => x.id));
1010
+ ORDEM.filter((id) => conhecidos.has(id)).map((id) => ESTILOS.find((x) => x.id === id)).filter(Boolean).forEach((e, i) => {
1011
+ if (i % 2 === 0) { coluna = el('div', { class: 'coluna' }); grade.appendChild(coluna); }
1012
+ const palco = el('div', { class: 'palco' });
1013
+ const card = el('button', { class: 'card', 'aria-pressed': 'false', 'aria-label': e.nome, id: 'card-' + e.id, style: `--cor:${cor(e.id)}` }, [
1014
+ el('span', { class: 'selo', text: 'escolhido' }), palco]);
1015
+ card.addEventListener('click', () => {
1016
+ const on = card.getAttribute('aria-pressed') !== 'true';
1017
+ card.setAttribute('aria-pressed', String(on));
1018
+ if (on) estado.escolhidos.set(e.id, estado.escolhidos.get(e.id) || { gostei: new Set(), texto: '' }); else estado.escolhidos.delete(e.id);
1019
+ atualizarConta();
1020
+ });
1021
+ coluna.appendChild(card);
1022
+ soNaTela(palco, montarCena(e.id, palco));
1023
+ });
1024
+ ligarSetas();
1025
+ }
1026
+
1027
+ /* só anima o que está à vista: são mais de 30 telinhas no carrossel */
1028
+ const vigia = 'IntersectionObserver' in window ? new IntersectionObserver((es) => es.forEach((x) => {
1029
+ (x.target.__anims || []).forEach((a) => (x.isIntersecting ? a.play() : a.pause()));
1030
+ }), { rootMargin: '0px 120px' }) : null;
1031
+ function soNaTela(palco, anims) { palco.__anims = anims; if (vigia) vigia.observe(palco); }
1032
+
1033
+ /* cada coluna tem uma vaga relativa ao começo da página: 0 a vistas-1 aparecem inteiras, a de cada lado
1034
+ aparece esmaecida e as outras ficam escondidas; avançar só muda o deslocamento, e a coluna que dá a volta
1035
+ pula de um lado pro outro sem animação, enquanto está invisível */
1036
+ function ligarSetas(t = $('#grade'), volta = $('#setaVolta'), vai = $('#setaVai'), caixa = $('#carrossel')) {
1037
+ const cols = [...t.children], N = cols.length, GAP = 20;
1038
+ let ini = 0, vistas = 3, passoX = 0, vagaAntes = new Map();
1039
+ const vaga = (i) => { let v = ((i - ini) % N + N) % N; if (v >= N - 2) v -= N; return v; };
1040
+ function medir() {
1041
+ const W = t.clientWidth;
1042
+ vistas = W < 560 ? 1 : W < 860 ? 2 : 3;
1043
+ const cw = (W - GAP * (vistas - 1)) / vistas;
1044
+ passoX = cw + GAP;
1045
+ cols.forEach((c) => { c.style.width = cw + 'px'; });
1046
+ t.style.height = (cols[0].offsetHeight || cw * 1.25 + GAP) + 'px';
1047
+ pintar(true);
1048
+ }
1049
+ function pintar(semAnim) {
1050
+ cols.forEach((c, i) => {
1051
+ const v = vaga(i), antes = vagaAntes.get(c);
1052
+ const salta = semAnim || (antes != null && Math.abs(v - antes) > vistas + 1);
1053
+ c.classList.toggle('salta', salta);
1054
+ const dentro = v >= 0 && v < vistas, vizinha = v === -1 || v === vistas;
1055
+ const op = dentro ? 1 : vizinha ? .4 : 0, esc = dentro ? 1 : .9;
1056
+ c.style.transform = `translateX(${v * passoX}px) scale(${esc})`;
1057
+ c.style.opacity = op;
1058
+ c.style.filter = dentro ? 'none' : 'blur(2px)';
1059
+ c.classList.toggle('vis', dentro);
1060
+ c.classList.toggle('longe', !dentro);
1061
+ vagaAntes.set(c, v);
1062
+ });
1063
+ if (semAnim) requestAnimationFrame(() => cols.forEach((c) => c.classList.remove('salta')));
1064
+ }
1065
+ const andar = (n) => { ini = ((ini + n * vistas) % N + N) % N; pintar(false); };
1066
+ volta.addEventListener('click', () => andar(-1));
1067
+ vai.addEventListener('click', () => andar(1));
1068
+ t.addEventListener('keydown', (ev) => { if (ev.key === 'ArrowRight') { ev.preventDefault(); andar(1); } if (ev.key === 'ArrowLeft') { ev.preventDefault(); andar(-1); } });
1069
+ /* arrastar com o dedo ou o trackpad também passa a página */
1070
+ let x0 = null;
1071
+ t.addEventListener('pointerdown', (ev) => { x0 = ev.clientX; });
1072
+ let arrastou = false;
1073
+ addEventListener('pointerup', (ev) => { if (x0 == null) return; const dx = ev.clientX - x0; x0 = null; if (Math.abs(dx) > 50) { arrastou = true; andar(dx < 0 ? 1 : -1); } });
1074
+ t.addEventListener('click', (ev) => { if (arrastou) { arrastou = false; ev.stopPropagation(); ev.preventDefault(); } }, true);
1075
+ let rodaPronta = true;
1076
+ caixa.addEventListener('wheel', (ev) => {
1077
+ if (Math.abs(ev.deltaX) < Math.abs(ev.deltaY) || Math.abs(ev.deltaX) < 20) return;
1078
+ ev.preventDefault();
1079
+ if (rodaPronta) { rodaPronta = false; andar(ev.deltaX > 0 ? 1 : -1); setTimeout(() => { rodaPronta = true; }, 700); }
1080
+ }, { passive: false });
1081
+ try { new ResizeObserver(medir).observe(t); } catch { addEventListener('resize', medir); }
1082
+ medir();
1083
+ }
1084
+
1085
+ function mostrar(n) {
1086
+ [1, 2, 3, 4].forEach((i) => { $('#passo' + i).hidden = i !== n; });
1087
+ const t = { 1: ['Qual movimento tem a sua cara?', 'Toque nos estilos que você curtiu. Pode ser mais de um. O PrimoCode aprende com isso e usa nos próximos vídeos.'],
1088
+ 2: ['Quase lá. Mais disso ou menos disso?', ''],
1089
+ 3: ['E o que não pode ter?', 'Isso pesa contra no diretor. Marca só o que te incomoda de verdade.'],
1090
+ 4: ['Pronto. Seu próximo vídeo já sai com a sua cara.', ''] }[n];
1091
+ $('#titulo').textContent = t[0];
1092
+ window.scrollTo({ top: 0, behavior: parar ? 'auto' : 'smooth' });
1093
+ }
1094
+
1095
+ function chip(texto, ligado, aoTrocar) {
1096
+ const c = el('button', { class: 'chip', 'aria-pressed': String(ligado), text: texto });
1097
+ c.addEventListener('click', () => { const on = c.getAttribute('aria-pressed') !== 'true'; c.setAttribute('aria-pressed', String(on)); aoTrocar(on); });
1098
+ return c;
1099
+ }
1100
+
1101
+ /* ── passo 2 ────────────────────────────────────────────────────────────── */
1102
+ function svgSeta(d) {
1103
+ const NS = ['http:', '', 'www.w3.org', '2000', 'svg'].join('/'), s = document.createElementNS(NS, 'svg'), p = document.createElementNS(NS, 'path');
1104
+ s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('aria-hidden', 'true'); p.setAttribute('d', d); s.appendChild(p); return s;
1105
+ }
1106
+ const RISCOS = { rapido: 3, lento: 2 };
1107
+ function opcao(k, t, ligado, aoTrocar) {
1108
+ const ic = el('span', { class: 'ic ic-' + k, 'aria-hidden': 'true' }, Array.from({ length: RISCOS[k] || 1 }, () => el('i')));
1109
+ const b = el('button', { class: 'op', 'aria-pressed': String(ligado) }, [ic, el('span', { text: t })]);
1110
+ b.addEventListener('click', () => { const on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', String(on)); aoTrocar(on); });
1111
+ return b;
1112
+ }
1113
+ /* uma pergunta só, valendo pra todos os escolhidos: escolher 15 não vira 15 perguntas */
1114
+ const geral = { gostei: new Set(), texto: '' };
1115
+ function passo2() {
1116
+ const lista = $('#lista2'); lista.replaceChildren();
1117
+ const ids = [...estado.escolhidos.keys()];
1118
+ let fila;
1119
+ if (ids.length <= 3) {
1120
+ fila = el('div', { class: 'mini-fila' });
1121
+ ids.forEach((id) => { const palco = el('div', { class: 'palco' }); fila.appendChild(palco); soNaTela(palco, montarCena(id, palco)); });
1122
+ } else {
1123
+ /* o carrossel infinito precisa de um vizinho de cada lado: com 4 ou 5, repete a fila */
1124
+ const voltas = ids.length < 6 ? ids.concat(ids) : ids;
1125
+ const trilho = el('div', { class: 'trilho', tabindex: '0', 'aria-label': 'Os motions que você escolheu' },
1126
+ voltas.map((id) => { const palco = el('div', { class: 'palco' }); soNaTela(palco, montarCena(id, palco)); return el('div', { class: 'coluna' }, [palco]); }));
1127
+ const seta = (lado, rot) => el('button', { class: 'seta seta-' + lado, 'aria-label': rot }, [svgSeta(lado === 'vai' ? 'M9 5l7 7-7 7' : 'M15 5l-7 7 7 7')]);
1128
+ const volta = seta('volta', 'Voltar'), vai = seta('vai', 'Avançar');
1129
+ fila = el('div', { class: 'carrossel' }, [volta, trilho, vai]);
1130
+ requestAnimationFrame(() => ligarSetas(trilho, volta, vai, fila));
1131
+ }
1132
+ const ops = (lista, tem, poe) => el('div', { class: 'ops' }, lista.map(([k, t]) => opcao(k, t, tem(k), poe(k))));
1133
+ const mais = ops(GOSTEI, (k) => geral.gostei.has(k), (k) => (on) => on ? geral.gostei.add(k) : geral.gostei.delete(k));
1134
+ const menos = ops(NAO_QUERO, (k) => estado.nao.has(k), (k) => (on) => on ? estado.nao.add(k) : estado.nao.delete(k));
1135
+ lista.append(fila,
1136
+ el('div', { class: 'item item-ops' }, [el('div', { class: 'grupo mais' }, [el('h3', { text: 'Mais disso' }), mais]), el('div', { class: 'grupo menos' }, [el('h3', { text: 'Menos disso' }), menos])]));
1137
+ }
1138
+
1139
+ /* ── passo 3 ────────────────────────────────────────────────────────────── */
1140
+ function passo3() {
1141
+ const box = $('#chipsNao'); if (box.childElementCount) return;
1142
+ NAO_QUERO.forEach(([k, t]) => box.appendChild(chip(t, estado.nao.has(k), (on) => on ? estado.nao.add(k) : estado.nao.delete(k))));
1143
+ const ta = $('#naoTexto');
1144
+ ta.addEventListener('input', (ev) => { estado.naoTexto = ev.target.value.slice(0, 280); });
1145
+ $('#sugNao').replaceChildren(...sugestoes(SUG_NAO, ta, (v) => { estado.naoTexto = v; }).children);
1146
+ montarCena('nao', $('#palcoNao'));
1147
+ }
1148
+
1149
+ /* ── o gosto vira pesos (o mesmo cálculo que o diretor vai usar) ─────────── */
1150
+ function gostoJson(pulou = false) {
1151
+ return { versao: 1, quando: new Date().toISOString(), pulou,
1152
+ escolhidos: [...estado.escolhidos].map(([id]) => ({ id, gostei: [...geral.gostei], texto: '' })), gostei: [...geral.gostei], texto: geral.texto.trim(),
1153
+ nao_quero: [...estado.nao], nao_texto: estado.naoTexto.trim(), catalogo: [...estado.extras] };
1154
+ }
1155
+ function pesos(g) {
1156
+ const p = { ritmo: { rapido: 1, medio: 1, lento: 1 }, cameras: {}, trocas: {}, tipografias: {}, overshoot: null };
1157
+ const mult = (o, k, m) => { o[k] = Math.min(5, (o[k] || 1) * m); };
1158
+ const chips = new Set(g.escolhidos.flatMap((x) => x.gostei));
1159
+ g.escolhidos.forEach(({ id }) => {
1160
+ const e = ESTILOS.find((x) => x.id === id);
1161
+ mult(p.ritmo, e.ritmo, 1.8);
1162
+ e.cameras.forEach((c) => mult(p.cameras, c, 2.2)); e.trocas.forEach((t) => mult(p.trocas, t, 2.2)); e.tipografias.forEach((t) => mult(p.tipografias, t, 2.2));
1163
+ });
1164
+ (g.catalogo || []).forEach((k) => { const [gr, id] = k.split(':'); if (p[gr]) mult(p[gr], id, 2.2); });
1165
+ if (chips.has('rapido')) mult(p.ritmo, 'rapido', 1.5);
1166
+ if (chips.has('calmo')) { mult(p.ritmo, 'lento', 1.5); mult(p.trocas, 'tremor', .4); mult(p.trocas, 'chicote', .4); }
1167
+ if (chips.has('quique')) { mult(p.trocas, 'giro', 1.4); mult(p.trocas, 'encolhe', 1.4); }
1168
+ if (chips.has('tres_d')) { mult(p.cameras, 'orbita', 1.5); mult(p.cameras, 'cinema', 1.5); mult(p.trocas, 'giro', 1.4); }
1169
+ if (chips.has('camera')) ['pulso', 'chicote', 'orbita'].forEach((c) => mult(p.cameras, c, 1.3));
1170
+ if (chips.has('cortes')) { mult(p.trocas, 'corte', 1.4); mult(p.trocas, 'chicote', 1.4); }
1171
+ if (g.nao_quero.includes('lento')) mult(p.ritmo, 'lento', .3);
1172
+ if (g.nao_quero.includes('liso')) { mult(p.trocas, 'dissolve', .4); mult(p.trocas, 'desfoque', .4); }
1173
+ if (g.nao_quero.includes('exagero')) { mult(p.trocas, 'tremor', .4); mult(p.trocas, 'chicote', .4); }
1174
+ if (g.nao_quero.includes('tremido')) mult(p.trocas, 'tremor', .2);
1175
+ if (g.escolhidos.length) {
1176
+ let o = g.escolhidos.reduce((s, { id }) => s + ESTILOS.find((x) => x.id === id).overshoot, 0) / g.escolhidos.length;
1177
+ if (chips.has('quique')) o += .05; if (g.nao_quero.includes('exagero')) o -= .05;
1178
+ p.overshoot = Math.max(0, Math.min(.3, o));
1179
+ }
1180
+ return p;
1181
+ }
1182
+ const topo = (o, n) => Object.entries(o).filter(([, v]) => v > 1).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k]) => k.replace('_', ' '));
1183
+ function resumo(g) {
1184
+ const nomeG = Object.fromEntries(GOSTEI), nomeN = Object.fromEntries(NAO_QUERO);
1185
+ const partes = g.escolhidos.map(({ id }) => ESTILOS.find((x) => x.id === id).nome);
1186
+ const doCat = (g.catalogo || []).map((k) => { const [gr, id] = k.split(':'); const c = CATALOGO.find((x) => x.grupo === gr); const it = c && c.itens.find((x) => x[0] === id); return it ? it[1] : id; });
1187
+ if (doCat.length) partes.push(`${doCat.join(', ')} do catálogo`);
1188
+ let s = `Você curtiu ${partes.join(' e ')}.`;
1189
+ if (g.gostei && g.gostei.length) s += ` O que pegou: ${g.gostei.map((k) => nomeG[k]).join(', ')}.`;
1190
+ if (g.nao_quero.length) s += ` Não quer ${g.nao_quero.map((k) => nomeN[k]).join(', ')}.`;
1191
+ const txt = [g.texto, g.nao_texto].filter(Boolean);
1192
+ if (txt.length) s += ` Nas suas palavras: “${txt.join(' · ')}”.`;
1193
+ return s;
1194
+ }
1195
+
1196
+ /* ── passo 4 ────────────────────────────────────────────────────────────── */
1197
+
1198
+
1199
+ $('#ir2').addEventListener('click', () => { passo2(); mostrar(2); });
1200
+ $('#volta1').addEventListener('click', () => mostrar(1));
1201
+ let salvando = false;
1202
+ $('#ir3').addEventListener('click', () => {
1203
+ if (salvando) return;
1204
+ salvando = true; erro(''); $('#ir3').disabled = true;
1205
+ mandar('/salvar', gostoJson()).then(() => mostrar(4)).catch((e) => {
1206
+ salvando = false; $('#ir3').disabled = false; $('#ir3').textContent = 'Tentar de novo';
1207
+ erro('Não consegui salvar: ' + e.message + '. O PrimoCode ainda está aberto no terminal?');
1208
+ });
1209
+ });
1210
+ $('#pular').addEventListener('click', () => {
1211
+ mandar('/pular', {}).then(() => {
1212
+ mostrar(4);
1213
+ $('#titulo').textContent = 'Tudo bem, pulei.';
1214
+ $('#fimSub').textContent = 'O PrimoCode segue sem gosto salvo. Pra escolher depois: /gosto refazer.';
1215
+ }).catch((e) => erro('Não consegui pular: ' + e.message));
1216
+ });
1217
+ $('#volta2').addEventListener('click', () => mostrar(estado.escolhidos.size ? 2 : 1));
1218
+ $('#salvar').addEventListener('click', () => { passo4(); mostrar(4); });
1219
+ $('#finalizar').addEventListener('click', (ev) => {
1220
+ const b = ev.currentTarget;
1221
+ try { window.close(); } catch { /* aba que o navegador não deixa fechar */ }
1222
+ setTimeout(() => { $('#fimSub').textContent = 'Pode fechar esta aba. O PrimoCode já está com o seu gosto.'; b.hidden = true; }, 250);
1223
+ });
1224
+ /* ── catálogo completo: todos os tipos de motion do diretor, animados e escolhíveis ── */
1225
+ const CATALOGO = [
1226
+ { grupo: 'trocas', titulo: 'Transições', sub: 'como uma cena vira a outra', itens: [['encolhe', 'encolhe'], ['empurra', 'empurra'], ['zoom', 'zoom'], ['persiana', 'persiana'], ['chicote', 'chicote'], ['desfoque', 'desfoque'], ['cortina', 'cortina'], ['giro', 'giro'], ['tremor', 'tremor'], ['abre', 'abre'], ['dissolve', 'dissolve'], ['corte', 'corte seco']] },
1227
+ { grupo: 'cameras', titulo: 'Câmeras', sub: 'como a câmera se mexe', itens: [['deriva', 'deriva'], ['pulso', 'pulso'], ['trilho', 'trilho'], ['orbita', 'órbita'], ['push', 'push'], ['chicote', 'chicote'], ['cinema', 'cinema']] },
1228
+ { grupo: 'tipografias', titulo: 'Tipografias', sub: 'como a letra entra', itens: [['editorial', 'editorial'], ['poster', 'pôster'], ['tecnica', 'técnica'], ['manuscrita', 'manuscrita'], ['pesada', 'pesada'], ['serifa_fina', 'serifa fina'], ['arredondada', 'arredondada'], ['geometrica', 'geométrica']] },
1229
+ ];
1230
+ const TIPO = { trocas: 'Transição', cameras: 'Câmera', tipografias: 'Tipografia' };
1231
+ function montarCatalogo() {
1232
+ const grade = $('#grade');
1233
+ CATALOGO.forEach(({ grupo, sub, itens }) => {
1234
+ itens.forEach(([id, nome]) => {
1235
+ const chave = grupo + ':' + id, palco = el('div', { class: 'palco' });
1236
+ const t = el('button', { class: 'card tile', 'aria-pressed': String(estado.extras.has(chave)), style: '--cor:var(--giz)' },
1237
+ [el('span', { class: 'selo', text: 'escolhido' }), palco, el('div', { class: 'rotulo' }, [el('span', { class: 'tipo', text: TIPO[grupo] }), el('b', { text: nome }), el('small', { text: sub })])]);
1238
+ t.addEventListener('click', () => {
1239
+ const on = !estado.extras.has(chave);
1240
+ if (on) estado.extras.add(chave); else estado.extras.delete(chave);
1241
+ t.setAttribute('aria-pressed', String(on));
1242
+ atualizarConta();
1243
+ });
1244
+ grade.appendChild(t);
1245
+ soNaTela(palco, montarPrevia(grupo, id, palco));
1246
+ });
1247
+ });
1248
+ }
1249
+ function atualizarConta() {
1250
+ const n = estado.escolhidos.size, x = estado.extras.size, partes = [];
1251
+ if (n) partes.push(`${n} estilo${n > 1 ? 's' : ''}`);
1252
+ if (x) partes.push(`${x} do catálogo`);
1253
+ $('#ir2').disabled = n + x === 0;
1254
+ }
1255
+
1256
+ /* No produto, a tela é aberta pelo próprio PrimoCode (servidor local com token) e o
1257
+ gosto vai num POST /salvar, que grava ~/.primocode/gosto-motion.json sozinho.
1258
+ Neste protótipo não há servidor, então só guarda na memória da página. */
1259
+ function mandar(rota, corpo) {
1260
+ return fetch(rota, { method: 'POST', headers: { 'content-type': 'application/json', 'x-gosto-token': TOKEN }, body: JSON.stringify(corpo) })
1261
+ .then((r) => r.json().then((j) => { if (!r.ok || !j.ok) throw new Error(j.error || ('erro ' + r.status)); return j; }));
1262
+ }
1263
+ function erro(msg) { const e = $('#erro'); e.hidden = !msg; e.textContent = msg || ''; }
1264
+
1265
+ passo1();
1266
+
1267
+ /* ── o fundo: ondas 3D em degradê, desenhadas na GPU ───────────────────────
1268
+ Seis camadas de onda, de trás pra frente; cada uma tem luz pela inclinação
1269
+ (difusa + brilho na crista), então a superfície parece ter volume. */
1270
+ (function ondas() {
1271
+ const cv = $('#ondas');
1272
+ const gl = cv.getContext('webgl', { antialias: false, premultipliedAlpha: false });
1273
+ if (!gl) return; // sem WebGL: fica o degradê do CSS
1274
+ const vs = 'attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}';
1275
+ const fs = `precision mediump float;
1276
+ uniform vec2 r; uniform float t; uniform vec3 ceu, clara, media, funda; uniform vec3 mouse;
1277
+ float ruido(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
1278
+ void main(){
1279
+ vec2 uv = gl_FragCoord.xy / r;
1280
+ float asp = r.x / r.y;
1281
+ float x = (uv.x - 0.5) * asp;
1282
+ float lado = clamp(abs(x) / (0.5 * asp), 0.0, 1.0);
1283
+ /* a onda se mexe sozinha o tempo todo: quatro senos em velocidades diferentes e a crista inteira respirando;
1284
+ o mouse ainda puxa a crista e abre um anel por onde passa */
1285
+ float crista = 0.15 + 0.26 * smoothstep(0.0, 1.0, lado) + 0.035 * sin(t * 0.8)
1286
+ + 0.085 * sin(x * 1.7 + t * 1.15) + 0.05 * sin(x * 3.3 - t * 1.6 + 1.3)
1287
+ + 0.028 * sin(x * 6.1 + t * 2.4) + 0.012 * sin(x * 11.0 - t * 3.1);
1288
+ float perto = exp(-pow((x - mouse.x) / 0.32, 2.0)) * mouse.z;
1289
+ crista += perto * (0.07 + 0.22 * clamp(mouse.y - crista, 0.0, 0.6));
1290
+ float dm = length(vec2(x - mouse.x, uv.y - mouse.y));
1291
+ crista += 0.014 * sin(dm * 26.0 - t * 5.0) * exp(-dm * 3.5) * mouse.z;
1292
+ float d = uv.y - crista;
1293
+ vec3 col = ceu;
1294
+ col = mix(col, media, exp(-pow(d / 0.17, 2.0)) * 0.95);
1295
+ float abaixo = smoothstep(0.03, -0.34, d);
1296
+ col = mix(col, funda, abaixo * (0.65 + 0.3 * lado));
1297
+ /* a fita de luz: uma segunda onda fina, mais rápida, que corre por cima da primeira */
1298
+ float c2 = crista + 0.075 + 0.04 * sin(x * 2.6 - t * 1.9 + 2.0);
1299
+ col = mix(col, clara, exp(-pow((uv.y - c2) / 0.012, 2.0)) * 0.75);
1300
+ float c3 = crista - 0.06 + 0.035 * sin(x * 3.4 + t * 2.2 + 0.7);
1301
+ col = mix(col, clara, exp(-pow((uv.y - c3) / 0.01, 2.0)) * 0.45 * (0.6 + 0.4 * sin(x * 5.0 - t * 2.0)));
1302
+ /* brilho correndo na crista */
1303
+ float brilho = exp(-pow((d + 0.015) / 0.035, 2.0)) * (0.55 + 0.45 * sin(x * 7.0 - t * 3.2));
1304
+ col = mix(col, clara, brilho * 0.6);
1305
+ col = mix(col, clara, exp(-dm * dm / 0.02) * 0.4 * mouse.z * smoothstep(0.2, -0.1, d));
1306
+ col = mix(col, ceu, smoothstep(0.24, 0.62, d));
1307
+ col += (ruido(gl_FragCoord.xy + fract(t * 0.5)) - 0.5) * 0.03;
1308
+ gl_FragColor = vec4(col, 1.0);
1309
+ }`;
1310
+ const sh = (tipo, src) => { const s = gl.createShader(tipo); gl.shaderSource(s, src); gl.compileShader(s); return gl.getShaderParameter(s, gl.COMPILE_STATUS) ? s : null; };
1311
+ const v = sh(gl.VERTEX_SHADER, vs), f = sh(gl.FRAGMENT_SHADER, fs);
1312
+ if (!v || !f) return;
1313
+ const pr = gl.createProgram(); gl.attachShader(pr, v); gl.attachShader(pr, f); gl.linkProgram(pr);
1314
+ if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) return;
1315
+ gl.useProgram(pr);
1316
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
1317
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
1318
+ const lp = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(lp); gl.vertexAttribPointer(lp, 2, gl.FLOAT, false, 0, 0);
1319
+ const U = (n) => gl.getUniformLocation(pr, n);
1320
+ const uR = U('r'), uT = U('t'), uM = U('mouse');
1321
+ const alvo = [0, .5, 0], m = [0, .5, 0]; // x na largura da onda, y de baixo pra cima, força (some quando o mouse para)
1322
+ let parado = 0;
1323
+ addEventListener('pointermove', (ev) => { alvo[0] = (ev.clientX / innerWidth - .5) * (innerWidth / innerHeight); alvo[1] = 1 - ev.clientY / innerHeight; alvo[2] = 1; parado = performance.now(); }, { passive: true });
1324
+ const rgb = (h) => { h = h.trim().replace('#', ''); if (h.length === 3) h = h.replace(/./g, '$&$&'); const n = parseInt(h, 16); return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255]; };
1325
+ function cores() {
1326
+ const cs = getComputedStyle(document.documentElement);
1327
+ [['ceu', '--onda-ceu'], ['clara', '--onda-clara'], ['media', '--onda-media'], ['funda', '--onda-funda']].forEach(([u, v]) => gl.uniform3fv(U(u), rgb(cs.getPropertyValue(v) || '#ffffff')));
1328
+ }
1329
+ function medir() {
1330
+ const d = Math.min(window.devicePixelRatio || 1, 1.5) * 0.75;
1331
+ cv.width = Math.max(1, Math.round(innerWidth * d)); cv.height = Math.max(1, Math.round(innerHeight * d));
1332
+ gl.viewport(0, 0, cv.width, cv.height); gl.uniform2f(uR, cv.width, cv.height);
1333
+ }
1334
+ const quadro = (ms) => {
1335
+ if (performance.now() - parado > 1800) alvo[2] = 0;
1336
+ for (let i = 0; i < 3; i++) m[i] += (alvo[i] - m[i]) * (i === 2 ? .04 : .08);
1337
+ gl.uniform3f(uM, m[0], m[1], m[2]); gl.uniform1f(uT, ms / 1000); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
1338
+ };
1339
+ cores(); medir();
1340
+ addEventListener('resize', () => { medir(); if (parar) quadro(4000); });
1341
+ matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { cores(); if (parar) quadro(4000); });
1342
+ new MutationObserver(() => { cores(); if (parar) quadro(4000); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
1343
+ if (parar) { quadro(4000); return; } // reduzir movimento: as ondas ficam paradas
1344
+ const tick = (ms) => { if (!document.hidden) quadro(ms); requestAnimationFrame(tick); };
1345
+ requestAnimationFrame(tick);
1346
+ })();
1347
+ </script>
1348
+
1349
+ </body></html>