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.
- package/LICENSES/edit-ia-pro.txt +32 -0
- package/LICENSES/fontes-OFL.txt +476 -0
- package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
- package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
- package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
- package/LICENSES/three-MIT.txt +23 -0
- package/LICENSES/video-use-MIT.txt +24 -0
- package/LICENSES/woven-sfx-CC0.txt +16 -0
- package/LICENSES/yunet-MIT.txt +21 -0
- package/README.md +2 -2
- package/bin/primocode.js +74 -9
- package/editor/LICENSE.md +11 -0
- package/editor/README.md +321 -0
- package/editor/SKILL.md +307 -0
- package/editor/THIRD_PARTY_NOTICES.md +228 -0
- package/editor/assets/baixar.json +591 -0
- package/editor/assets/fonts/anton/OFL.txt +93 -0
- package/editor/assets/fonts/caveat/OFL.txt +93 -0
- package/editor/assets/fonts/figtree/OFL.txt +93 -0
- package/editor/assets/fonts/fonts.json +10 -0
- package/editor/assets/fonts/montserrat/OFL.txt +93 -0
- package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
- package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
- package/editor/assets/models/YUNET_LICENSE.txt +21 -0
- package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
- package/editor/assets/sfx/licencas/cash.txt +76 -0
- package/editor/assets/sfx/licencas/charge.txt +31 -0
- package/editor/assets/sfx/licencas/click.txt +46 -0
- package/editor/assets/sfx/licencas/coin.txt +50 -0
- package/editor/assets/sfx/licencas/count.txt +47 -0
- package/editor/assets/sfx/licencas/data.txt +31 -0
- package/editor/assets/sfx/licencas/error.txt +32 -0
- package/editor/assets/sfx/licencas/flash.txt +31 -0
- package/editor/assets/sfx/licencas/gears.txt +82 -0
- package/editor/assets/sfx/licencas/keyboard.txt +48 -0
- package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
- package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
- package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
- package/editor/assets/sfx/licencas/notification.txt +54 -0
- package/editor/assets/sfx/licencas/pop.txt +38 -0
- package/editor/assets/sfx/licencas/rewind.txt +32 -0
- package/editor/assets/sfx/licencas/rise.txt +46 -0
- package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
- package/editor/assets/sfx/licencas/snap.txt +50 -0
- package/editor/assets/sfx/licencas/success.txt +40 -0
- package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
- package/editor/assets/sfx/licencas/tick.txt +14 -0
- package/editor/assets/sfx/licencas/whoosh.txt +47 -0
- package/editor/assets/sfx/licencas/wind.txt +31 -0
- package/editor/assets/sfx/sfx.json +832 -0
- package/editor/assets/sfx/sfx_sinteticos.json +317 -0
- package/editor/assets/sfx/sfx_woven.json +387 -0
- package/editor/assets/sfx/woven/LICENCA.md +16 -0
- package/editor/fichas/briefing-do-video.md +45 -0
- package/editor/fichas/conferencia-da-amostra.md +56 -0
- package/editor/fichas/matriz-de-ganchos.csv +3 -0
- package/editor/fichas/padrao.json +16 -0
- package/editor/fichas/planilha-de-cenas.csv +4 -0
- package/editor/fichas/planilha-de-versoes.csv +4 -0
- package/editor/fichas/plano-em-blocos.md +32 -0
- package/editor/fichas/registro-de-materiais.json +27 -0
- package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
- package/editor/helpers/bordas_precisas.py +272 -0
- package/editor/helpers/camera_viva.py +426 -0
- package/editor/helpers/captions.py +601 -0
- package/editor/helpers/common.py +308 -0
- package/editor/helpers/compose_final.py +3555 -0
- package/editor/helpers/conferir_corte.py +234 -0
- package/editor/helpers/conferir_dados.py +405 -0
- package/editor/helpers/contact_sheet.py +282 -0
- package/editor/helpers/corte_por_onda.py +433 -0
- package/editor/helpers/corte_seco.py +595 -0
- package/editor/helpers/denoise.py +971 -0
- package/editor/helpers/efeitos.py +408 -0
- package/editor/helpers/efeitos_gerar.py +159 -0
- package/editor/helpers/elevenlabs_api.py +442 -0
- package/editor/helpers/face_layout.py +570 -0
- package/editor/helpers/fetch_images.py +423 -0
- package/editor/helpers/ganho_por_trecho.py +214 -0
- package/editor/helpers/gen_image.py +312 -0
- package/editor/helpers/gen_music.py +316 -0
- package/editor/helpers/gen_sfx.py +148 -0
- package/editor/helpers/grade.py +307 -0
- package/editor/helpers/icloud.py +119 -0
- package/editor/helpers/make_sfx.py +940 -0
- package/editor/helpers/matte.py +1342 -0
- package/editor/helpers/medir_referencia.py +2232 -0
- package/editor/helpers/mixar_trilha.py +354 -0
- package/editor/helpers/montar_corte.py +295 -0
- package/editor/helpers/motion/__init__.py +13 -0
- package/editor/helpers/motion/engine.py +1915 -0
- package/editor/helpers/motion/registry.py +510 -0
- package/editor/helpers/motion/render.py +665 -0
- package/editor/helpers/motion/templates/barra_progresso.py +370 -0
- package/editor/helpers/motion/templates/carimbo.py +132 -0
- package/editor/helpers/motion/templates/checklist.py +271 -0
- package/editor/helpers/motion/templates/comentario_cta.py +380 -0
- package/editor/helpers/motion/templates/contador.py +222 -0
- package/editor/helpers/motion/templates/destaque.py +263 -0
- package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
- package/editor/helpers/motion/templates/grafico_barras.py +510 -0
- package/editor/helpers/motion/templates/grafico_linha.py +462 -0
- package/editor/helpers/motion/templates/notificacao.py +266 -0
- package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
- package/editor/helpers/motion/templates/passos.py +321 -0
- package/editor/helpers/motion/templates/riscado.py +298 -0
- package/editor/helpers/motion/templates/selo_garantia.py +150 -0
- package/editor/helpers/motion_render.py +241 -0
- package/editor/helpers/motion_sugerir.py +961 -0
- package/editor/helpers/noise_check.py +134 -0
- package/editor/helpers/pack_transcripts.py +223 -0
- package/editor/helpers/palavras_na_onda.py +176 -0
- package/editor/helpers/plano_sfx.py +839 -0
- package/editor/helpers/portao.py +427 -0
- package/editor/helpers/preview_server.py +1177 -0
- package/editor/helpers/rastrear_rosto.py +298 -0
- package/editor/helpers/render_cut.py +1256 -0
- package/editor/helpers/respiros.py +89 -0
- package/editor/helpers/sincronizar_cameras.py +571 -0
- package/editor/helpers/speech_regions.py +423 -0
- package/editor/helpers/style_catalog.json +231 -0
- package/editor/helpers/style_samples.py +246 -0
- package/editor/helpers/tela_verde.py +2113 -0
- package/editor/helpers/text_render.py +2059 -0
- package/editor/helpers/texto_na_tela.py +366 -0
- package/editor/helpers/timeline_view.py +292 -0
- package/editor/helpers/tira_de_quadros.py +113 -0
- package/editor/helpers/transcribe.py +734 -0
- package/editor/helpers/transcribe_batch.py +129 -0
- package/editor/helpers/trilhas.py +1146 -0
- package/editor/helpers/trilhas_humores.json +66 -0
- package/editor/helpers/valores.py +187 -0
- package/editor/helpers/verify_cut.py +820 -0
- package/editor/helpers/voice_levels.py +233 -0
- package/editor/helpers/voz_check.py +386 -0
- package/editor/helpers/voz_limpa.py +81 -0
- package/editor/helpers/watch_preview.py +372 -0
- package/editor/modelo-projeto/LEIA.md +402 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
- package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
- package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
- package/editor/modelo-projeto/assets/three/LICENSE +21 -0
- package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
- package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
- package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
- package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
- package/editor/modelo-projeto/build.py +4828 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
- package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
- package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
- package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
- package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
- package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
- package/editor/modelo-projeto/hyperframes.json +12 -0
- package/editor/modelo-projeto/package.json +11 -0
- package/editor/modelo-projeto/projeto.json +16 -0
- package/editor/preview/app.css +1009 -0
- package/editor/preview/app.js +2122 -0
- package/editor/preview/index.html +307 -0
- package/editor/pyproject.toml +23 -0
- package/editor/references/3d.md +236 -0
- package/editor/references/animacao-catalogo.md +672 -0
- package/editor/references/animacao.md +498 -0
- package/editor/references/comandos.md +92 -0
- package/editor/references/corte-seco-como-funciona.md +50 -0
- package/editor/references/corte-seco.md +88 -0
- package/editor/references/corte.md +290 -0
- package/editor/references/direcao-por-blocos.md +218 -0
- package/editor/references/enquadramento.md +654 -0
- package/editor/references/estilos-prontos.md +355 -0
- package/editor/references/estudio.md +100 -0
- package/editor/references/exemplo-eventos.json +67 -0
- package/editor/references/lettering.md +252 -0
- package/editor/references/modelos.md +494 -0
- package/editor/references/phase2.md +1007 -0
- package/editor/references/referencia-aplicar.md +203 -0
- package/editor/references/referencia-medir.md +375 -0
- package/editor/references/revisao.md +335 -0
- package/editor/references/som-e-trilha.md +146 -0
- package/editor/references/tabela-de-sons.md +130 -0
- package/editor/references/variacoes.md +234 -0
- package/editor/uv.lock +1141 -0
- package/lib/act.js +38 -8
- package/lib/app-chat.js +97 -0
- package/lib/catalogo.js +7 -1
- package/lib/chamada-torta.js +81 -0
- package/lib/editor-pro.js +786 -0
- package/lib/entrega.js +58 -0
- package/lib/estudio/conferencia-visual.js +76 -0
- package/lib/estudio/diretor.js +6 -1
- package/lib/estudio/exportar.js +3 -1
- package/lib/estudio/gosto/tela.html +1349 -0
- package/lib/estudio/gosto.js +305 -0
- package/lib/estudio/index.js +12 -1
- package/lib/estudio/kit-ae/base.jsx +119 -13
- package/lib/estudio/kit-ae/cenas.jsx +47 -15
- package/lib/estudio/kit-ae/guarda.jsx +274 -0
- package/lib/estudio/kit-ae/primitivas.jsx +180 -24
- package/lib/estudio/maquina-diretor.js +70 -12
- package/lib/estudio/maquina-foto.js +177 -0
- package/lib/estudio/maquina-guarda.js +207 -0
- package/lib/estudio/maquina-layout.js +22 -1
- package/lib/estudio/maquina.js +64 -11
- package/lib/estudio/referencias.js +1 -1
- package/lib/estudio/visao/visao.swift +97 -0
- package/lib/estudio/visao.js +60 -0
- package/lib/headless.js +15 -3
- package/lib/local/cerebro.json +285 -7
- package/lib/local/motor.js +5 -2
- package/lib/memoria-usuario.js +164 -0
- package/lib/nuvem.js +33 -2
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +19 -11
- package/lib/protecao.js +134 -0
- package/lib/repeticao.js +1 -1
- package/lib/sandbox.js +57 -9
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +23 -4
- package/lib/ui.js +2 -2
- package/package.json +3 -2
- package/skills/editar-meu-video/SKILL.md +6 -2
- package/skills/editar-video/SKILL.md +113 -0
- package/studio/pessoa.py +52 -0
- package/voz/primo_voz/__main__.py +55 -40
- package/voz/primo_voz/app.py +500 -4
- package/voz/primo_voz/chat.html +1773 -0
- package/voz/primo_voz/conversa.py +0 -26
- package/voz/primo_voz/hud.html +8 -2
- package/voz/primo_voz/hud.py +129 -5
- package/voz/primo_voz/identidade.py +1 -1
- package/voz/primo_voz/painel_dados.py +134 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-BR"><head><meta charset="UTF-8"/><title>k2d · antes e depois na mesma escala (desenho)</title></head>
|
|
3
|
+
<body>
|
|
4
|
+
<!--
|
|
5
|
+
Kit 2D (c): antes e depois como desenho, com os dois lados na mesma escala, na mesma base e com rótulos
|
|
6
|
+
literais. O "antes" aparece e fica parado; o "depois" entra embaixo (ou ao lado), alinhado pela mesma borda;
|
|
7
|
+
só no fim a diferença acende: o que saiu fica vermelho e o espaço que sobrou fica tracejado (faixa), ou um anel
|
|
8
|
+
marca o mesmo lugar nas duas imagens (imagem). Os dois lados têm a mesma nitidez, cor e tamanho: a comparação
|
|
9
|
+
só vale se a única diferença na tela for a que a fala aponta.
|
|
10
|
+
Serve para "gravei duas vezes, ficou a melhor", "tirou os silêncios", dois prints do mesmo lugar antes e
|
|
11
|
+
depois. Para comparar dois VÍDEOS, use o bloco "comparar" do projeto (references/enquadramento.md).
|
|
12
|
+
Guia: references/animacao.md ("Kit 2D"). Para usar: copie para compositions/cenas/<nome>.html, troque
|
|
13
|
+
"k2d_antes_depois" (template, raiz e ID) e o prefixo "k2dd-", mude os PARÂMETROS e ligue no projeto.json em
|
|
14
|
+
"cenas_proprias".
|
|
15
|
+
Fora do 9:16, a raiz leva o tamanho do vídeo (data-width="1920" data-height="1080" no 16:9); o check
|
|
16
|
+
acusa se esquecer.
|
|
17
|
+
-->
|
|
18
|
+
<template id="k2d_antes_depois-template">
|
|
19
|
+
<style>
|
|
20
|
+
@font-face{font-family:Montserrat;src:url("assets/fontes/Montserrat.ttf") format("truetype");font-weight:100 900;font-display:block}
|
|
21
|
+
@font-face{font-family:"JetBrains Mono";src:url("assets/fontes/JetBrainsMono.ttf") format("truetype");font-weight:100 800;font-display:block}
|
|
22
|
+
#root{position:absolute;inset:0;overflow:hidden;font-family:Montserrat}
|
|
23
|
+
#k2dd-fundo{position:absolute;inset:0;opacity:0}
|
|
24
|
+
.k2dd-lado{position:absolute;opacity:0}
|
|
25
|
+
.k2dd-rotulo{position:absolute;display:block;font-weight:900;white-space:nowrap}
|
|
26
|
+
.k2dd-detalhe{position:absolute;display:block;font-weight:700;white-space:nowrap}
|
|
27
|
+
.k2dd-valor{position:absolute;display:block;font-family:"JetBrains Mono";font-weight:700;text-align:right;white-space:nowrap}
|
|
28
|
+
.k2dd-trilho{position:absolute}
|
|
29
|
+
.k2dd-fala{position:absolute;overflow:hidden}
|
|
30
|
+
.k2dd-barra{position:absolute}
|
|
31
|
+
.k2dd-respiro{position:absolute}
|
|
32
|
+
.k2dd-silencio{position:absolute;left:0;right:0}
|
|
33
|
+
.k2dd-marca{position:absolute;inset:0;opacity:0}
|
|
34
|
+
.k2dd-nome{position:absolute;display:block;font-weight:700;text-align:center;white-space:nowrap}
|
|
35
|
+
.k2dd-sobra{position:absolute;box-sizing:border-box;opacity:0}
|
|
36
|
+
.k2dd-caixa{position:absolute;overflow:hidden;box-sizing:border-box}
|
|
37
|
+
.k2dd-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
38
|
+
.k2dd-anel{position:absolute;box-sizing:border-box;opacity:0}
|
|
39
|
+
</style>
|
|
40
|
+
<div id="root" data-composition-id="k2d_antes_depois" data-width="1080" data-height="1920" data-duration="5">
|
|
41
|
+
<div id="k2dd-fundo"></div>
|
|
42
|
+
<div id="k2dd-palco"></div>
|
|
43
|
+
</div>
|
|
44
|
+
<script>
|
|
45
|
+
(function () {
|
|
46
|
+
const ID = "k2d_antes_depois";
|
|
47
|
+
// ------------------------------------------------------------------ PARÂMETROS (troque aqui)
|
|
48
|
+
// Tempo: número (segundos da cena), "palavra" ou {palavra, depois, padrao}. UMA palavra, procurada na fala do
|
|
49
|
+
// trecho sem acento e pelo começo; "depois" pula as repetições anteriores; "padrao" vale quando a palavra não
|
|
50
|
+
// aparece (cena aberta sozinha no estúdio, transcrição que mudou).
|
|
51
|
+
const PARAM = {
|
|
52
|
+
tipo: "faixa", // "faixa" (duas faixas de tempo) ou "imagem" (dois arquivos de imagem do projeto)
|
|
53
|
+
disposicao: "auto", // "empilhado" ou "lado"; auto: faixa sempre empilhada, imagem lado a lado em tela deitada
|
|
54
|
+
proporcao: 1, // imagem: largura / altura das duas imagens (precisam ser iguais; 9/16 para print de celular)
|
|
55
|
+
// os dois lados. rotulo: literal e neutro, o que cada lado É ("GRAVADO", "NO VÍDEO"), nunca um juízo ("RUIM").
|
|
56
|
+
// detalhe: uma linha menor (null = sem). valor: número medido, ou null. em: quando o lado aparece.
|
|
57
|
+
// faixa: os trechos em ordem, { fala: tamanho, nome: "1ª vez", sai: true } ou { respiro: tamanho } (o respiro
|
|
58
|
+
// sempre sai); tamanhos relativos, a mesma unidade nos dois lados. imagem: arquivo: "assets/antes.png"
|
|
59
|
+
antes: { rotulo: "GRAVADO", detalhe: null, valor: null, em: { palavra: "gravei", padrao: 0.3 }, arquivo: null,
|
|
60
|
+
trechos: [{ fala: 2.0, nome: "1ª vez", sai: true }, { respiro: 0.6 }, { fala: 2.0, nome: "2ª vez" }] },
|
|
61
|
+
depois: { rotulo: "NO VÍDEO", detalhe: null, valor: null, em: { palavra: "ficou", padrao: 1.8 }, arquivo: null,
|
|
62
|
+
trechos: [{ fala: 2.0, nome: "2ª vez" }] },
|
|
63
|
+
// a diferença, acesa só no fim. faixa: o que saiu fica vermelho e a sobra do "depois" fica tracejada.
|
|
64
|
+
// imagem: um anel no mesmo lugar das duas, caixa [x, y, largura, altura] de 0 a 1. null = sem destaque
|
|
65
|
+
diferenca: { em: { palavra: "melhor", padrao: 3.0 }, caixa: null },
|
|
66
|
+
pausa: 0.6, // tudo parado pelo menos isso depois de cada ação
|
|
67
|
+
legenda: true, // tela cheia: deixa livre a faixa da legenda (a cena usa os 70% de cima da zona segura)
|
|
68
|
+
fundo: "auto", // "auto" (papel em tela cheia, transparente na moldura), "papel", "#rrggbb" ou null
|
|
69
|
+
area: null, // [x0, y0, x1, y1] em px; null = área livre da moldura ou a zona segura
|
|
70
|
+
entrada: 0.2,
|
|
71
|
+
saida: 0.25,
|
|
72
|
+
};
|
|
73
|
+
// ------------------------------------------------------------------ dados do build.py (padrões para abrir sozinha)
|
|
74
|
+
const C = Object.assign({ palavras: [], duracao: 5, largura: 1080, altura: 1920, seguro: [140, 230, 940, 1530], rosto: null, moldura: null,
|
|
75
|
+
cores: { papel: "#fffdfd", linha: "#e6ddf8", base: "#c7b7ef", destaque: "#665080", tinta: "#352c40", verde: "#2e9e5b", vermelho: "#e5484d" } },
|
|
76
|
+
(window.__cenas || {})[ID] || {});
|
|
77
|
+
const DUR = C.duracao, U = Math.min(C.largura, C.altura) / 1080;
|
|
78
|
+
// a raiz precisa do tamanho do vídeo: em 16:9 (ou outro formato) troque data-width e data-height da raiz, senão a cena sai cortada
|
|
79
|
+
const raiz = document.getElementById("k2dd-fundo").parentElement;
|
|
80
|
+
if (raiz.offsetWidth && (Math.abs(raiz.offsetWidth - C.largura) > 2 || Math.abs(raiz.offsetHeight - C.altura) > 2))
|
|
81
|
+
console.error(`[${ID}] a raiz da cena tem ${raiz.offsetWidth} x ${raiz.offsetHeight} e o vídeo ${C.largura} x ${C.altura}: troque data-width e data-height da raiz`);
|
|
82
|
+
// ------------------------------------------------------------------ ajudantes do kit 2D (iguais em todas as cenas do kit; não precisa mexer)
|
|
83
|
+
const sem = (s) => String(s).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
84
|
+
const palavra = (txt, depois, padrao) => { const a = sem(txt), p = a && (C.palavras || []).find((w) => w.t >= depois - 1e-3 && sem(w.texto).startsWith(a)); return p ? p.t : padrao; };
|
|
85
|
+
const momento = (m, padrao) => (typeof m === "number" ? m : typeof m === "string" ? palavra(m, 0, padrao)
|
|
86
|
+
: m && typeof m === "object" ? palavra(m.palavra || "", m.depois == null ? 0 : momento(m.depois, 0), m.padrao == null ? padrao : m.padrao) : padrao);
|
|
87
|
+
// fila: uma ação de cada vez; a próxima espera a anterior terminar e ficar parada PARAM.pausa segundos
|
|
88
|
+
let fimUltima = C.moldura ? Math.max(0, C.moldura.fechada_em || 0) : 0, livre = fimUltima + PARAM.entrada;
|
|
89
|
+
function agenda(quer, dur, colada) {
|
|
90
|
+
const t = Math.max(quer, colada ? fimUltima : livre);
|
|
91
|
+
if (t - quer > 0.4) console.warn(`[${ID}] uma ação entrou ${(t - quer).toFixed(2)} s depois da palavra: afaste as palavras ou diminua "pausa"`);
|
|
92
|
+
fimUltima = t + dur; livre = fimUltima + PARAM.pausa; return t;
|
|
93
|
+
}
|
|
94
|
+
const cor = (n) => C.cores[n] || n;
|
|
95
|
+
const rgb = (h) => { h = String(h).replace("#", ""); if (h.length === 3) h = h.replace(/./g, "$&$&"); return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)); };
|
|
96
|
+
const mistura = (a, b, k) => "#" + rgb(a).map((v, i) => Math.round(v + (rgb(b)[i] - v) * k).toString(16).padStart(2, "0")).join("");
|
|
97
|
+
// texto: largura estimada da Montserrat até o peso 900, com folga (a fonte ainda não carregou quando a cena monta), quebra e o maior tamanho que cabe
|
|
98
|
+
const peso = (ch) => { const b = ch.normalize("NFD")[0]; return b === " " ? 0.28 : "MW".includes(b) ? 1 : "mw".includes(b) ? 0.92 : "iljI.,:;'!|".includes(b) ? 0.32 : /[A-Z]/.test(b) ? 0.76 : /[0-9]/.test(b) ? 0.68 : 0.62; };
|
|
99
|
+
const larg = (t, px) => [...String(t)].reduce((s, ch) => s + peso(ch), 0) * px * 1.1;
|
|
100
|
+
const linhas = (t, px, w) => String(t).split(/\s+/).filter(Boolean).reduce((ls, p) => { const u = ls[ls.length - 1]; if (u !== undefined && larg(u + " " + p, px) <= w) ls[ls.length - 1] = u + " " + p; else ls.push(p); return ls; }, []);
|
|
101
|
+
const cabe = (t, w, h, maior, menor, max) => { for (let px = Math.round(maior); px > menor; px--) { const ls = linhas(t, px, w); if (ls.length <= max && ls.every((l) => larg(l, px) <= w) && ls.length * px * 1.16 <= h) return px; } return Math.round(menor); };
|
|
102
|
+
// onde a cena fica: PARAM.area, a área livre da moldura ou a zona segura (sem a faixa da legenda, com PARAM.legenda)
|
|
103
|
+
const A = PARAM.area ? PARAM.area.slice() : C.moldura && C.moldura.area_livre ? C.moldura.area_livre.slice()
|
|
104
|
+
: [C.seguro[0], C.seguro[1], C.seguro[2], PARAM.legenda ? Math.round(C.seguro[1] + (C.seguro[3] - C.seguro[1]) * 0.7) : C.seguro[3]];
|
|
105
|
+
const AW = A[2] - A[0], AH = A[3] - A[1];
|
|
106
|
+
const fundo = PARAM.fundo === "auto" ? (C.moldura ? null : "papel") : PARAM.fundo;
|
|
107
|
+
function pintaFundo(el) {
|
|
108
|
+
if (fundo === "papel" && C.fundo_css) el.style.cssText += ";" + C.fundo_css; // estilo escuro: o fundo do próprio estilo (grade e brilho)
|
|
109
|
+
else if (fundo === "papel") { const g = Math.round(60 * U); Object.assign(el.style, { backgroundColor: C.cores.papel, backgroundSize: `${g}px ${g}px`, backgroundImage: `linear-gradient(${C.cores.linha} 2px,transparent 2px),linear-gradient(90deg,${C.cores.linha} 2px,transparent 2px)` }); }
|
|
110
|
+
else if (fundo) el.style.backgroundColor = fundo;
|
|
111
|
+
}
|
|
112
|
+
const novo = (tag, id, cls, pai, estilo, texto) => { const e = document.createElement(tag); if (id) e.id = id; if (cls) e.className = cls; if (estilo) Object.assign(e.style, estilo); if (texto != null) e.textContent = texto; pai.appendChild(e); return e; };
|
|
113
|
+
const px = (v) => `${v}px`;
|
|
114
|
+
|
|
115
|
+
// ------------------------------------------------------------------ medidas: os dois lados do mesmo tamanho, a mesma escala e a mesma borda
|
|
116
|
+
pintaFundo(document.getElementById("k2dd-fundo"));
|
|
117
|
+
const palco = document.getElementById("k2dd-palco"), faixa = PARAM.tipo !== "imagem";
|
|
118
|
+
const lado = PARAM.disposicao === "lado" || (PARAM.disposicao === "auto" && !faixa && AW > AH * 1.25);
|
|
119
|
+
const u = U * Math.min(1, AH / (faixa ? 600 * U : AH));
|
|
120
|
+
const lh = 70 * u, fsR = 52 * u, fsD = 32 * u, fsV = 42 * u, gap = (faixa ? 70 : 40) * u, hf = 150 * u, hn = 46 * u;
|
|
121
|
+
let bw, bh; // a caixa de cada lado (faixa ou imagem), igual nos dois
|
|
122
|
+
if (faixa) { bw = Math.min(AW, 900 * u); bh = hf; }
|
|
123
|
+
else {
|
|
124
|
+
const livreW = lado ? (AW - gap) / 2 : AW, livreH = lado ? AH - lh - 12 * u : (AH - gap) / 2 - lh - 12 * u;
|
|
125
|
+
bw = Math.min(livreW, livreH * PARAM.proporcao); bh = bw / PARAM.proporcao;
|
|
126
|
+
}
|
|
127
|
+
const blocoH = lh + 12 * u + bh + (faixa ? hn : 0);
|
|
128
|
+
const totW = lado ? 2 * bw + gap : bw, totH = lado ? blocoH : 2 * blocoH + gap;
|
|
129
|
+
const X0 = A[0] + (AW - totW) / 2, Y0 = A[1] + (AH - totH) / 2;
|
|
130
|
+
const total = (L) => L.trechos.reduce((s, q) => s + (q.fala || q.respiro || 0), 0);
|
|
131
|
+
const esc = faixa ? bw / Math.max(total(PARAM.antes), total(PARAM.depois)) : 1;
|
|
132
|
+
let semente = 11; // onda sempre igual (sorteio com semente fixa: o mesmo desenho em todo render)
|
|
133
|
+
const sorteio = () => { semente = (semente * 16807) % 2147483647; return (semente - 1) / 2147483646; };
|
|
134
|
+
const lados = {}, marcas = [], aneis = [], imgs = [];
|
|
135
|
+
["antes", "depois"].forEach((k, n) => {
|
|
136
|
+
const L = PARAM[k], x = X0 + (lado ? n * (bw + gap) : 0), y = Y0 + (lado ? 0 : n * (blocoH + gap));
|
|
137
|
+
const el = novo("div", `k2dd-${k}`, "k2dd-lado", palco, { left: px(x), top: px(y), width: px(bw), height: px(blocoH) });
|
|
138
|
+
const larV = L.valor ? larg(L.valor, fsV) : 0, larR = Math.min(bw - larV - 20 * u, larg(L.rotulo, fsR));
|
|
139
|
+
const fr = cabe(L.rotulo, larR + 2, lh, fsR, 24 * u, 1);
|
|
140
|
+
novo("span", `k2dd-rotulo-${k}`, "k2dd-rotulo", el, { left: 0, top: 0, height: px(lh), lineHeight: px(lh), color: C.cores.tinta, fontSize: px(fr) }, L.rotulo);
|
|
141
|
+
if (L.detalhe) {
|
|
142
|
+
const xd = larg(L.rotulo, fr) + 18 * u, wd = bw - xd - larV - 20 * u;
|
|
143
|
+
if (wd > 60 * u) novo("span", `k2dd-detalhe-${k}`, "k2dd-detalhe", el, { left: px(xd), top: 0, width: px(wd), height: px(lh), lineHeight: px(lh + 6 * u), color: C.cores.destaque, fontSize: px(cabe(L.detalhe, wd, lh, fsD, 18 * u, 1)) }, L.detalhe);
|
|
144
|
+
}
|
|
145
|
+
if (L.valor) novo("span", `k2dd-valor-${k}`, "k2dd-valor", el, { right: 0, top: 0, width: px(larV), height: px(lh), lineHeight: px(lh), color: C.cores.tinta, fontSize: px(fsV) }, L.valor);
|
|
146
|
+
const by = lh + 12 * u;
|
|
147
|
+
if (faixa) {
|
|
148
|
+
const tr = novo("div", `k2dd-trilho-${k}`, "k2dd-trilho", el, { left: 0, top: px(by), width: px(bw), height: px(bh), borderRadius: px(16 * u), background: mistura(C.cores.linha, C.cores.papel, 0.3) });
|
|
149
|
+
let xx = 0;
|
|
150
|
+
L.trechos.forEach((q, j) => {
|
|
151
|
+
const w = (q.fala || q.respiro || 0) * esc;
|
|
152
|
+
if (q.fala) {
|
|
153
|
+
const f = novo("div", `k2dd-fala-${k}-${j}`, "k2dd-fala", tr, { left: px(xx), top: px(bh * 0.1), width: px(w), height: px(bh * 0.8), borderRadius: px(12 * u), background: mistura(C.cores.base, C.cores.papel, 0.45) });
|
|
154
|
+
const passo = 15 * u, nb = Math.max(1, Math.floor((w - 10 * u) / passo));
|
|
155
|
+
for (let b = 0; b < nb; b++) { const h = bh * 0.8 * (0.22 + 0.68 * sorteio()); novo("div", null, "k2dd-barra", f, { left: px(5 * u + b * passo + (w - 10 * u - nb * passo) / 2), top: px((bh * 0.8 - h) / 2), width: px(8 * u), height: px(h), borderRadius: px(4 * u), background: C.cores.destaque }); }
|
|
156
|
+
if (q.nome && larg(q.nome, 28 * u) < w - 8 * u) novo("span", `k2dd-nome-${k}-${j}`, "k2dd-nome", el, { left: px(xx), top: px(by + bh + 8 * u), width: px(w), height: px(hn - 8 * u), lineHeight: px(hn - 8 * u), color: C.cores.tinta, fontSize: px(28 * u) }, q.nome);
|
|
157
|
+
if (q.sai) marcas.push(novo("div", `k2dd-marca-${k}-${j}`, "k2dd-marca", f, { borderRadius: px(12 * u), background: `repeating-linear-gradient(135deg, ${C.cores.vermelho}cc 0 ${10 * u}px, ${C.cores.vermelho}55 ${10 * u}px ${20 * u}px)` }));
|
|
158
|
+
} else {
|
|
159
|
+
const g = novo("div", `k2dd-respiro-${k}-${j}`, "k2dd-respiro", tr, { left: px(xx), top: px(bh * 0.1), width: px(w), height: px(bh * 0.8) });
|
|
160
|
+
novo("div", null, "k2dd-silencio", g, { top: px(bh * 0.4 - 3 * u), height: px(6 * u), background: C.cores.base });
|
|
161
|
+
marcas.push(novo("div", `k2dd-marca-${k}-${j}`, "k2dd-marca", g, { borderRadius: px(10 * u), background: `repeating-linear-gradient(135deg, ${C.cores.vermelho} 0 ${10 * u}px, ${mistura(C.cores.vermelho, C.cores.papel, 0.45)} ${10 * u}px ${20 * u}px)` }));
|
|
162
|
+
}
|
|
163
|
+
xx += w;
|
|
164
|
+
});
|
|
165
|
+
// a sobra: o espaço que o "depois" deixou livre até onde o "antes" ia, tracejado
|
|
166
|
+
const fimAntes = total(PARAM.antes) * esc;
|
|
167
|
+
if (k === "depois" && fimAntes - xx > 8 * u) lados.sobra = novo("div", "k2dd-sobra", "k2dd-sobra", tr, { left: px(xx + 4 * u), top: px(bh * 0.1), width: px(fimAntes - xx - 4 * u), height: px(bh * 0.8), borderRadius: px(12 * u), border: `${Math.max(3, 4 * u)}px dashed ${C.cores.vermelho}` });
|
|
168
|
+
} else {
|
|
169
|
+
const cx = novo("div", `k2dd-caixa-${k}`, "k2dd-caixa", el, { left: 0, top: px(by), width: px(bw), height: px(bh), borderRadius: px(18 * u), background: C.cores.papel, border: `${Math.max(3, 4 * u)}px solid ${C.cores.base}` });
|
|
170
|
+
if (L.arquivo) { const im = novo("img", `k2dd-img-${k}`, "k2dd-img", cx); im.alt = L.rotulo; im.src = L.arquivo; imgs.push(im); }
|
|
171
|
+
const cxd = PARAM.diferenca && PARAM.diferenca.caixa;
|
|
172
|
+
if (cxd) aneis.push(novo("div", `k2dd-anel-${k}`, "k2dd-anel", cx, { left: px(cxd[0] * bw), top: px(cxd[1] * bh), width: px(cxd[2] * bw), height: px(cxd[3] * bh), borderRadius: px(18 * u), border: `${Math.max(5, 8 * u)}px solid ${C.cores.vermelho}` }));
|
|
173
|
+
}
|
|
174
|
+
lados[k] = el;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ------------------------------------------------------------------ a linha do tempo
|
|
178
|
+
const tl = gsap.timeline({ paused: true });
|
|
179
|
+
tl.fromTo("#k2dd-fundo", { opacity: 0 }, { opacity: 1, duration: Math.max(0.01, PARAM.entrada), ease: "power1.out" }, 0);
|
|
180
|
+
tl.fromTo("#k2dd-palco", { opacity: 0 }, { opacity: 1, duration: 0.01 }, 0);
|
|
181
|
+
["antes", "depois"].forEach((k, n) => {
|
|
182
|
+
const t = agenda(momento(PARAM[k].em, n ? 1.8 : 0.3) - 0.1, 0.4);
|
|
183
|
+
tl.fromTo(lados[k], { opacity: 0, y: 30 * u }, { opacity: 1, y: 0, duration: 0.4, ease: "power2.out" }, t);
|
|
184
|
+
});
|
|
185
|
+
if (PARAM.diferenca) {
|
|
186
|
+
const itens = marcas.concat(lados.sobra ? [lados.sobra] : [], aneis), d = 0.3 + 0.07 * Math.max(0, itens.length - 1);
|
|
187
|
+
const t = agenda(momento(PARAM.diferenca.em, 3.0) - 0.1, d);
|
|
188
|
+
// nada anima escala no kit: o Chrome guarda o desenho da escala antiga por um tempo e o render deixa de sair igual
|
|
189
|
+
itens.forEach((m, j) => tl.fromTo(m, { opacity: 0, clipPath: "inset(50% 0% 50% 0%)" }, { opacity: 1, clipPath: "inset(0% 0% 0% 0%)", duration: 0.3, ease: "power2.out" }, t + 0.07 * j));
|
|
190
|
+
}
|
|
191
|
+
if (DUR - PARAM.saida - fimUltima < 0.6) console.warn(`[${ID}] a diferença acende a ${(DUR - PARAM.saida - fimUltima).toFixed(2)} s do fim da cena: deixe parado pelo menos 0,6 s (estenda o "ate" da cena)`);
|
|
192
|
+
if (PARAM.saida > 0) tl.fromTo(["#k2dd-fundo", "#k2dd-palco"], { opacity: 1 }, { opacity: 0, duration: PARAM.saida, ease: "power1.in", immediateRender: false }, Math.max(0, DUR - PARAM.saida));
|
|
193
|
+
// imagem: o render espera as duas imagens carregarem antes do primeiro quadro
|
|
194
|
+
if (imgs.length) ((window.__hf = window.__hf || {}).buildReady = window.__hf.buildReady || {})[ID] = Promise.all(imgs.map((im) => im.decode().catch(() => console.error(`[${ID}] não abri a imagem ${im.src}`))));
|
|
195
|
+
window.__timelines["k2d_antes_depois"] = tl;
|
|
196
|
+
})();
|
|
197
|
+
</script>
|
|
198
|
+
</template>
|
|
199
|
+
</body></html>
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-BR"><head><meta charset="UTF-8"/><title>k2d · arquivo que muda de estado (a faixa fica limpa)</title></head>
|
|
3
|
+
<body>
|
|
4
|
+
<!--
|
|
5
|
+
Kit 2D (a): um arquivo de vídeo que muda de estado sem mudar de desenho, de cor nem de lugar. Começa parado,
|
|
6
|
+
com a faixa da gravação cheia de respiros; a cada ação dita, só uma parte muda: os respiros acendem, depois
|
|
7
|
+
somem e a fala se junta (o relógio conta até a duração nova), e o selo troca de texto e de cor. Uma ação de
|
|
8
|
+
cada vez, com tudo parado depois de cada uma. O arquivo nunca pula de posição nem vira outro desenho.
|
|
9
|
+
Serve para "tirou os silêncios", "cortou os erros", "ficou mais curto": qualquer antes e depois que acontece
|
|
10
|
+
NO MESMO objeto. Para um celular com a conversa que muda, veja k2d_conversa.html.
|
|
11
|
+
Números (duração) só medidos (cut_timeline.json, a fonte): sem medida, "duracao": null.
|
|
12
|
+
Guia: references/animacao.md ("Kit 2D"). Para usar: copie para compositions/cenas/<nome>.html, troque
|
|
13
|
+
"k2d_arquivo" (template, raiz e ID) e o prefixo "k2da-", mude os PARÂMETROS e ligue no projeto.json em
|
|
14
|
+
"cenas_proprias".
|
|
15
|
+
Fora do 9:16, a raiz leva o tamanho do vídeo (data-width="1920" data-height="1080" no 16:9); o check
|
|
16
|
+
acusa se esquecer.
|
|
17
|
+
-->
|
|
18
|
+
<template id="k2d_arquivo-template">
|
|
19
|
+
<style>
|
|
20
|
+
@font-face{font-family:Montserrat;src:url("assets/fontes/Montserrat.ttf") format("truetype");font-weight:100 900;font-display:block}
|
|
21
|
+
@font-face{font-family:"JetBrains Mono";src:url("assets/fontes/JetBrainsMono.ttf") format("truetype");font-weight:100 800;font-display:block}
|
|
22
|
+
#root{position:absolute;inset:0;overflow:hidden;font-family:Montserrat}
|
|
23
|
+
#k2da-fundo{position:absolute;inset:0;opacity:0}
|
|
24
|
+
#k2da-cartao{position:absolute;box-sizing:border-box;opacity:0}
|
|
25
|
+
#k2da-icone{position:absolute}
|
|
26
|
+
#k2da-play{position:absolute;inset:28%;clip-path:polygon(18% 0,100% 50%,18% 100%)}
|
|
27
|
+
#k2da-nome{position:absolute;display:block;font-weight:800;white-space:nowrap}
|
|
28
|
+
#k2da-relogio{position:absolute;display:block;font-family:"JetBrains Mono";font-weight:700;text-align:right;white-space:nowrap}
|
|
29
|
+
#k2da-trilho{position:absolute;overflow:hidden}
|
|
30
|
+
#k2da-fim{position:absolute}
|
|
31
|
+
.k2da-fala{position:absolute;overflow:hidden}
|
|
32
|
+
.k2da-barra{position:absolute}
|
|
33
|
+
.k2da-respiro{position:absolute}
|
|
34
|
+
.k2da-silencio{position:absolute;left:0;right:0}
|
|
35
|
+
.k2da-marca{position:absolute;inset:0;opacity:0}
|
|
36
|
+
#k2da-selo{position:absolute;overflow:hidden}
|
|
37
|
+
.k2da-selo-cor{position:absolute;inset:0;opacity:0}
|
|
38
|
+
.k2da-selo-txt{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-weight:900;color:#fff;white-space:nowrap;opacity:0}
|
|
39
|
+
</style>
|
|
40
|
+
<div id="root" data-composition-id="k2d_arquivo" data-width="1080" data-height="1920" data-duration="6">
|
|
41
|
+
<div id="k2da-fundo"></div>
|
|
42
|
+
<div id="k2da-cartao">
|
|
43
|
+
<div id="k2da-icone"><div id="k2da-play"></div></div>
|
|
44
|
+
<span id="k2da-nome">gravacao.mp4</span>
|
|
45
|
+
<span id="k2da-relogio">​0:35</span>
|
|
46
|
+
<div id="k2da-trilho"><div id="k2da-fim"></div></div>
|
|
47
|
+
<div id="k2da-selo"></div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<script>
|
|
51
|
+
(function () {
|
|
52
|
+
const ID = "k2d_arquivo";
|
|
53
|
+
// ------------------------------------------------------------------ PARÂMETROS (troque aqui)
|
|
54
|
+
// Tempo: número (segundos da cena), "palavra" ou {palavra, depois, padrao}. UMA palavra, procurada na fala do
|
|
55
|
+
// trecho sem acento e pelo começo; "depois" pula as repetições anteriores; "padrao" vale quando a palavra não
|
|
56
|
+
// aparece (cena aberta sozinha no estúdio, transcrição que mudou).
|
|
57
|
+
const PARAM = {
|
|
58
|
+
nome: "gravacao.mp4", // o nome escrito no arquivo (o de verdade, ou um neutro)
|
|
59
|
+
// a faixa da gravação: os trechos de fala (tamanhos relativos) e os respiros entre eles (mesma unidade;
|
|
60
|
+
// um a menos que as falas). É um desenho, não uma medida: não escreva número em cima dela
|
|
61
|
+
falas: [3.2, 1.6, 2.4, 1.2, 2.8],
|
|
62
|
+
respiros: [1.1, 0.7, 1.4, 0.9],
|
|
63
|
+
duracao: { antes: "0:35", depois: "0:22" }, // só números medidos; null = sem relógio
|
|
64
|
+
selo: { antes: "COM RESPIROS", depois: "LIMPO", corAntes: "vermelho", corDepois: "verde" }, // null = sem selo
|
|
65
|
+
// as ações, uma de cada vez, na ordem da fala:
|
|
66
|
+
// "marca": os respiros acendem em vermelho (mostra o que vai sair)
|
|
67
|
+
// "fecha": os respiros somem e a fala se junta, no mesmo lugar; o relógio conta até a duração de depois
|
|
68
|
+
// "selo": o selo troca de texto e de cor
|
|
69
|
+
acoes: [
|
|
70
|
+
{ tipo: "marca", em: { palavra: "erro", padrao: 1.2 } },
|
|
71
|
+
{ tipo: "fecha", em: { palavra: "cortou", padrao: 2.6 } },
|
|
72
|
+
{ tipo: "selo", em: { palavra: "cortou", padrao: 3.8 } },
|
|
73
|
+
],
|
|
74
|
+
entra: 0, // quando o arquivo aparece (fica parado uns "pausa" segundos antes da 1ª ação)
|
|
75
|
+
pausa: 0.6, // tudo parado pelo menos isso depois de cada ação
|
|
76
|
+
legenda: true, // tela cheia: deixa livre a faixa da legenda (a cena usa os 70% de cima da zona segura)
|
|
77
|
+
fundo: "auto", // "auto" (papel em tela cheia, transparente na moldura), "papel", "#rrggbb" ou null
|
|
78
|
+
area: null, // [x0, y0, x1, y1] em px; null = área livre da moldura ou a zona segura
|
|
79
|
+
entrada: 0.2,
|
|
80
|
+
saida: 0.25,
|
|
81
|
+
};
|
|
82
|
+
// ------------------------------------------------------------------ dados do build.py (padrões para abrir sozinha)
|
|
83
|
+
const C = Object.assign({ palavras: [], duracao: 6, largura: 1080, altura: 1920, seguro: [140, 230, 940, 1530], rosto: null, moldura: null,
|
|
84
|
+
cores: { papel: "#fffdfd", linha: "#e6ddf8", base: "#c7b7ef", destaque: "#665080", tinta: "#352c40", verde: "#2e9e5b", vermelho: "#e5484d" } },
|
|
85
|
+
(window.__cenas || {})[ID] || {});
|
|
86
|
+
const DUR = C.duracao, U = Math.min(C.largura, C.altura) / 1080;
|
|
87
|
+
// a raiz precisa do tamanho do vídeo: em 16:9 (ou outro formato) troque data-width e data-height da raiz, senão a cena sai cortada
|
|
88
|
+
const raiz = document.getElementById("k2da-fundo").parentElement;
|
|
89
|
+
if (raiz.offsetWidth && (Math.abs(raiz.offsetWidth - C.largura) > 2 || Math.abs(raiz.offsetHeight - C.altura) > 2))
|
|
90
|
+
console.error(`[${ID}] a raiz da cena tem ${raiz.offsetWidth} x ${raiz.offsetHeight} e o vídeo ${C.largura} x ${C.altura}: troque data-width e data-height da raiz`);
|
|
91
|
+
// ------------------------------------------------------------------ ajudantes do kit 2D (iguais em todas as cenas do kit; não precisa mexer)
|
|
92
|
+
const sem = (s) => String(s).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
93
|
+
const palavra = (txt, depois, padrao) => { const a = sem(txt), p = a && (C.palavras || []).find((w) => w.t >= depois - 1e-3 && sem(w.texto).startsWith(a)); return p ? p.t : padrao; };
|
|
94
|
+
const momento = (m, padrao) => (typeof m === "number" ? m : typeof m === "string" ? palavra(m, 0, padrao)
|
|
95
|
+
: m && typeof m === "object" ? palavra(m.palavra || "", m.depois == null ? 0 : momento(m.depois, 0), m.padrao == null ? padrao : m.padrao) : padrao);
|
|
96
|
+
// fila: uma ação de cada vez; a próxima espera a anterior terminar e ficar parada PARAM.pausa segundos
|
|
97
|
+
let fimUltima = C.moldura ? Math.max(0, C.moldura.fechada_em || 0) : 0, livre = fimUltima + PARAM.entrada;
|
|
98
|
+
function agenda(quer, dur, colada) {
|
|
99
|
+
const t = Math.max(quer, colada ? fimUltima : livre);
|
|
100
|
+
if (t - quer > 0.4) console.warn(`[${ID}] uma ação entrou ${(t - quer).toFixed(2)} s depois da palavra: afaste as palavras ou diminua "pausa"`);
|
|
101
|
+
fimUltima = t + dur; livre = fimUltima + PARAM.pausa; return t;
|
|
102
|
+
}
|
|
103
|
+
const cor = (n) => C.cores[n] || n;
|
|
104
|
+
const rgb = (h) => { h = String(h).replace("#", ""); if (h.length === 3) h = h.replace(/./g, "$&$&"); return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16)); };
|
|
105
|
+
const mistura = (a, b, k) => "#" + rgb(a).map((v, i) => Math.round(v + (rgb(b)[i] - v) * k).toString(16).padStart(2, "0")).join("");
|
|
106
|
+
// texto: largura estimada da Montserrat até o peso 900, com folga (a fonte ainda não carregou quando a cena monta), quebra e o maior tamanho que cabe
|
|
107
|
+
const peso = (ch) => { const b = ch.normalize("NFD")[0]; return b === " " ? 0.28 : "MW".includes(b) ? 1 : "mw".includes(b) ? 0.92 : "iljI.,:;'!|".includes(b) ? 0.32 : /[A-Z]/.test(b) ? 0.76 : /[0-9]/.test(b) ? 0.68 : 0.62; };
|
|
108
|
+
const larg = (t, px) => [...String(t)].reduce((s, ch) => s + peso(ch), 0) * px * 1.1;
|
|
109
|
+
const linhas = (t, px, w) => String(t).split(/\s+/).filter(Boolean).reduce((ls, p) => { const u = ls[ls.length - 1]; if (u !== undefined && larg(u + " " + p, px) <= w) ls[ls.length - 1] = u + " " + p; else ls.push(p); return ls; }, []);
|
|
110
|
+
const cabe = (t, w, h, maior, menor, max) => { for (let px = Math.round(maior); px > menor; px--) { const ls = linhas(t, px, w); if (ls.length <= max && ls.every((l) => larg(l, px) <= w) && ls.length * px * 1.16 <= h) return px; } return Math.round(menor); };
|
|
111
|
+
// onde a cena fica: PARAM.area, a área livre da moldura ou a zona segura (sem a faixa da legenda, com PARAM.legenda)
|
|
112
|
+
const A = PARAM.area ? PARAM.area.slice() : C.moldura && C.moldura.area_livre ? C.moldura.area_livre.slice()
|
|
113
|
+
: [C.seguro[0], C.seguro[1], C.seguro[2], PARAM.legenda ? Math.round(C.seguro[1] + (C.seguro[3] - C.seguro[1]) * 0.7) : C.seguro[3]];
|
|
114
|
+
const AW = A[2] - A[0], AH = A[3] - A[1];
|
|
115
|
+
const fundo = PARAM.fundo === "auto" ? (C.moldura ? null : "papel") : PARAM.fundo;
|
|
116
|
+
function pintaFundo(el) {
|
|
117
|
+
if (fundo === "papel" && C.fundo_css) el.style.cssText += ";" + C.fundo_css; // estilo escuro: o fundo do próprio estilo (grade e brilho)
|
|
118
|
+
else if (fundo === "papel") { const g = Math.round(60 * U); Object.assign(el.style, { backgroundColor: C.cores.papel, backgroundSize: `${g}px ${g}px`, backgroundImage: `linear-gradient(${C.cores.linha} 2px,transparent 2px),linear-gradient(90deg,${C.cores.linha} 2px,transparent 2px)` }); }
|
|
119
|
+
else if (fundo) el.style.backgroundColor = fundo;
|
|
120
|
+
}
|
|
121
|
+
const novo = (tag, id, cls, pai, estilo, texto) => { const e = document.createElement(tag); if (id) e.id = id; if (cls) e.className = cls; if (estilo) Object.assign(e.style, estilo); if (texto != null) e.textContent = texto; pai.appendChild(e); return e; };
|
|
122
|
+
const px = (v) => `${v}px`;
|
|
123
|
+
// letra sobre uma cor: branca, como sempre; só num estilo escuro (tinta clara) a cor clara leva letra escura (o papel)
|
|
124
|
+
const claro = (h) => { const [r, g, b] = rgb(h); return 0.299 * r + 0.587 * g + 0.114 * b > 150; };
|
|
125
|
+
const sobre = (fundo) => (claro(C.cores.tinta) && claro(fundo) ? C.cores.papel : "#ffffff");
|
|
126
|
+
|
|
127
|
+
// ------------------------------------------------------------------ o arquivo: medidas (u cresce ou encolhe tudo junto, pela área)
|
|
128
|
+
const u = U * Math.min(1.4, AW / (860 * U), AH / (520 * U));
|
|
129
|
+
const P = 40 * u, hc = 84 * u, hf = 200 * u, hs = 72 * u, vao = 36 * u;
|
|
130
|
+
const W = Math.min(AW, 860 * u), H = P + hc + vao + hf + vao + hs + P, X = A[0] + (AW - W) / 2, Y = A[1] + (AH - H) / 2;
|
|
131
|
+
const $ = (id) => document.getElementById(id);
|
|
132
|
+
pintaFundo($("k2da-fundo"));
|
|
133
|
+
Object.assign($("k2da-cartao").style, { left: px(X), top: px(Y), width: px(W), height: px(H), borderRadius: px(34 * u), background: C.cores.papel,
|
|
134
|
+
border: `${Math.max(3, 4 * u)}px solid ${C.cores.base}`, boxShadow: `0 ${14 * u}px 0 -2px ${C.cores.base}` }); // sombra dura (a desfocada varia de um render para outro)
|
|
135
|
+
Object.assign($("k2da-icone").style, { left: px(P), top: px(P), width: px(hc), height: px(hc), borderRadius: px(20 * u), background: C.cores.destaque });
|
|
136
|
+
$("k2da-play").style.background = C.cores.papel;
|
|
137
|
+
const temRelogio = !!(PARAM.duracao && PARAM.duracao.antes);
|
|
138
|
+
const larRel = temRelogio ? Math.max(larg(PARAM.duracao.antes, 44 * u), larg(PARAM.duracao.depois || "", 44 * u)) : 0;
|
|
139
|
+
const larNome = W - 2 * P - hc - 24 * u - (temRelogio ? larRel + 24 * u : 0);
|
|
140
|
+
const $nome = $("k2da-nome"), $rel = $("k2da-relogio");
|
|
141
|
+
$nome.textContent = PARAM.nome;
|
|
142
|
+
Object.assign($nome.style, { left: px(P + hc + 24 * u), top: px(P), width: px(larNome), height: px(hc), lineHeight: px(hc), color: C.cores.tinta, fontSize: px(cabe(PARAM.nome, larNome, hc, 44 * u, 20 * u, 1)) });
|
|
143
|
+
if (!temRelogio) $rel.style.display = "none";
|
|
144
|
+
else {
|
|
145
|
+
$rel.textContent = "\u200B" + PARAM.duracao.antes;
|
|
146
|
+
Object.assign($rel.style, { right: px(P), top: px(P), width: px(larRel), height: px(hc), lineHeight: px(hc), color: C.cores.tinta, fontSize: px(44 * u) });
|
|
147
|
+
}
|
|
148
|
+
// a faixa: trechos de fala com a onda desenhada e os respiros entre eles, na mesma escala
|
|
149
|
+
const FW = W - 2 * P, fy = P + hc + vao, T = PARAM.falas.reduce((s, v) => s + v, 0) + PARAM.respiros.reduce((s, v) => s + v, 0), esc = FW / T;
|
|
150
|
+
Object.assign($("k2da-trilho").style, { left: px(P), top: px(fy), width: px(FW), height: px(hf), borderRadius: px(18 * u), background: mistura(C.cores.linha, C.cores.papel, 0.3) });
|
|
151
|
+
let semente = 7; // onda sempre igual (sorteio com semente fixa: o mesmo desenho em todo render)
|
|
152
|
+
const sorteio = () => { semente = (semente * 16807) % 2147483647; return (semente - 1) / 2147483646; };
|
|
153
|
+
const falas = [], respiros = [], antes = [];
|
|
154
|
+
let x = 0, somados = 0;
|
|
155
|
+
PARAM.falas.forEach((v, i) => {
|
|
156
|
+
antes.push(somados);
|
|
157
|
+
const w = v * esc, f = novo("div", `k2da-fala-${i}`, "k2da-fala", $("k2da-trilho"), { left: px(x), top: px(hf * 0.1), width: px(w), height: px(hf * 0.8), borderRadius: px(14 * u), background: mistura(C.cores.base, C.cores.papel, 0.45) });
|
|
158
|
+
const passo = 15 * u, n = Math.max(1, Math.floor((w - 10 * u) / passo));
|
|
159
|
+
for (let j = 0; j < n; j++) {
|
|
160
|
+
const h = hf * 0.8 * (0.22 + 0.68 * sorteio());
|
|
161
|
+
novo("div", null, "k2da-barra", f, { left: px(5 * u + j * passo + (w - 10 * u - n * passo) / 2), top: px((hf * 0.8 - h) / 2), width: px(8 * u), height: px(h), borderRadius: px(4 * u), background: C.cores.destaque });
|
|
162
|
+
}
|
|
163
|
+
falas.push(f);
|
|
164
|
+
x += w;
|
|
165
|
+
if (i < PARAM.respiros.length) {
|
|
166
|
+
const r = PARAM.respiros[i] * esc, g = novo("div", `k2da-respiro-${i}`, "k2da-respiro", $("k2da-trilho"), { left: px(x), top: px(hf * 0.1), width: px(r), height: px(hf * 0.8) });
|
|
167
|
+
novo("div", null, "k2da-silencio", g, { top: px(hf * 0.4 - 3 * u), height: px(6 * u), background: C.cores.base });
|
|
168
|
+
novo("div", `k2da-marca-${i}`, "k2da-marca", g, { borderRadius: px(10 * u), background: `repeating-linear-gradient(135deg, ${C.cores.vermelho} 0 ${10 * u}px, ${mistura(C.cores.vermelho, C.cores.papel, 0.45)} ${10 * u}px ${20 * u}px)` });
|
|
169
|
+
respiros.push(g);
|
|
170
|
+
x += r; somados += PARAM.respiros[i];
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
Object.assign($("k2da-fim").style, { left: px(x - 6 * u), top: 0, width: px(6 * u), height: px(hf), background: C.cores.tinta });
|
|
174
|
+
// o selo: largura do texto mais comprido, as duas cores empilhadas e os dois textos no mesmo lugar (um sai antes do outro entrar)
|
|
175
|
+
const S = PARAM.selo, $selo = $("k2da-selo");
|
|
176
|
+
const selo = {};
|
|
177
|
+
if (!S) $selo.style.display = "none";
|
|
178
|
+
else {
|
|
179
|
+
const fs = 34 * u, ws = Math.max(larg(S.antes, fs), larg(S.depois, fs)) + 64 * u;
|
|
180
|
+
Object.assign($selo.style, { left: px(P), top: px(fy + hf + vao), width: px(ws), height: px(hs), borderRadius: px(hs / 2) });
|
|
181
|
+
["antes", "depois"].forEach((k) => {
|
|
182
|
+
const c = mistura(cor(k === "antes" ? S.corAntes : S.corDepois), C.cores.tinta, 0.18);
|
|
183
|
+
selo["cor_" + k] = novo("div", `k2da-selo-cor-${k}`, "k2da-selo-cor", $selo, { background: c });
|
|
184
|
+
selo["txt_" + k] = novo("span", `k2da-selo-txt-${k}`, "k2da-selo-txt", $selo, { fontSize: px(fs), color: sobre(c) }, S[k]);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ------------------------------------------------------------------ a linha do tempo
|
|
189
|
+
const tl = gsap.timeline({ paused: true });
|
|
190
|
+
tl.fromTo("#k2da-fundo", { opacity: 0 }, { opacity: 1, duration: Math.max(0.01, PARAM.entrada), ease: "power1.out" }, 0);
|
|
191
|
+
const tE = agenda(momento(PARAM.entra, 0), 0.45);
|
|
192
|
+
// nada anima escala no kit: o Chrome guarda o desenho da escala antiga por um tempo e o render deixa de sair igual
|
|
193
|
+
tl.fromTo("#k2da-cartao", { opacity: 0, y: 60 * u }, { opacity: 1, y: 0, duration: 0.45, ease: "back.out(1.5)" }, tE);
|
|
194
|
+
if (S) tl.fromTo([selo.cor_antes, selo.txt_antes], { opacity: 0 }, { opacity: 1, duration: 0.01 }, tE);
|
|
195
|
+
const seg = (s) => s.split(":").reduce((acc, v) => acc * 60 + Number(v), 0);
|
|
196
|
+
const formata = (n, modelo) => { const partes = modelo.split(":").length, dig = modelo.split(":")[0].length; const out = []; let r = Math.round(n);
|
|
197
|
+
for (let k = 0; k < partes; k++) { out.unshift(String(r % 60).padStart(k === partes - 1 ? dig : 2, "0")); r = Math.floor(r / 60); } return out.join(":"); };
|
|
198
|
+
PARAM.acoes.forEach((a, n) => {
|
|
199
|
+
const quer = momento(a.em, 1 + n * 1.2) - 0.1;
|
|
200
|
+
if (a.tipo === "marca") {
|
|
201
|
+
const d = 0.3 + 0.07 * Math.max(0, respiros.length - 1), t = agenda(quer, d);
|
|
202
|
+
respiros.forEach((g, j) => tl.fromTo(g.lastChild, { opacity: 0, clipPath: "inset(50% 0% 50% 0%)" }, { opacity: 1, clipPath: "inset(0% 0% 0% 0%)", duration: 0.3, ease: "power2.out" }, t + 0.07 * j));
|
|
203
|
+
} else if (a.tipo === "fecha") {
|
|
204
|
+
const d = 0.7, t = agenda(quer, d), mov = { duration: d, ease: "power2.inOut" };
|
|
205
|
+
falas.forEach((f, i) => { if (antes[i] > 0) tl.fromTo(f, { x: 0 }, { x: -antes[i] * esc, ...mov }, t); });
|
|
206
|
+
let soma = 0;
|
|
207
|
+
// o respiro some fechando da direita para a esquerda enquanto a fala de depois anda por cima dele
|
|
208
|
+
respiros.forEach((g, j) => { tl.fromTo(g, { x: 0, clipPath: "inset(0% 0% 0% 0%)" }, { x: -soma * esc, clipPath: "inset(0% 100% 0% 0%)", ...mov }, t); soma += PARAM.respiros[j]; });
|
|
209
|
+
tl.fromTo("#k2da-fim", { x: 0 }, { x: -soma * esc, ...mov }, t);
|
|
210
|
+
if (temRelogio && PARAM.duracao.depois) {
|
|
211
|
+
const a0 = seg(PARAM.duracao.antes), a1 = seg(PARAM.duracao.depois), passos = Math.max(1, Math.round(d * 15));
|
|
212
|
+
for (let k = 1; k <= passos; k++) {
|
|
213
|
+
const p = k / passos, e = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
|
214
|
+
tl.set($rel, { textContent: "\u200B" + (k === passos ? PARAM.duracao.depois : formata(a0 + (a1 - a0) * e, PARAM.duracao.antes)) }, t + p * d);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} else if (a.tipo === "selo" && S) {
|
|
218
|
+
const t = agenda(quer, 0.55);
|
|
219
|
+
tl.to(selo.txt_antes, { opacity: 0, duration: 0.15, ease: "power1.in", immediateRender: false }, t);
|
|
220
|
+
// a cor nova passa como uma cortina da esquerda para a direita (misturar as duas cores por transparência dá um tom sujo no meio)
|
|
221
|
+
tl.fromTo(selo.cor_depois, { opacity: 1, clipPath: "inset(0% 100% 0% 0%)" }, { clipPath: "inset(0% 0% 0% 0%)", duration: 0.3, ease: "power2.inOut" }, t + 0.1);
|
|
222
|
+
tl.fromTo(selo.txt_depois, { opacity: 0, y: 14 * u }, { opacity: 1, y: 0, duration: 0.2, ease: "power2.out" }, t + 0.35);
|
|
223
|
+
tl.fromTo($selo, { y: 0 }, { y: -8 * u, duration: 0.12, ease: "power1.out", yoyo: true, repeat: 1 }, t + 0.35);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
if (DUR - PARAM.saida - fimUltima < 0.6) console.warn(`[${ID}] a última ação termina a ${(DUR - PARAM.saida - fimUltima).toFixed(2)} s do fim da cena: deixe o resultado parado pelo menos 0,6 s (estenda o "ate" da cena)`);
|
|
227
|
+
if (PARAM.saida > 0) tl.fromTo(["#k2da-fundo", "#k2da-cartao"], { opacity: 1 }, { opacity: 0, duration: PARAM.saida, ease: "power1.in", immediateRender: false }, Math.max(0, DUR - PARAM.saida));
|
|
228
|
+
window.__timelines["k2d_arquivo"] = tl;
|
|
229
|
+
})();
|
|
230
|
+
</script>
|
|
231
|
+
</template>
|
|
232
|
+
</body></html>
|