primocode 10.16.0 → 10.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +28 -8
- 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 +36 -8
- package/lib/catalogo.js +4 -0
- 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.js +45 -4
- package/lib/estudio/index.js +8 -5
- 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 +61 -7
- 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 +60 -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/local/cerebro.json +192 -6
- package/lib/local/motor.js +5 -2
- package/lib/mcp.js +105 -7
- package/lib/nuvem.js +11 -1
- package/lib/outra-pasta.js +1 -1
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +29 -12
- package/lib/protecao.js +134 -0
- package/lib/sandbox.js +11 -4
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +3 -0
- package/lib/ui.js +1 -1
- 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/app.py +43 -4
- package/voz/primo_voz/chat.html +17 -6
- package/voz/primo_voz/painel_dados.py +134 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""notificacao: a generic phone notification card that slides down from the top of the screen.
|
|
2
|
+
|
|
3
|
+
Title and text come from the person; the app icon is a generic glyph (speech bubble, bell, check, heart or a
|
|
4
|
+
letter) on the accent color, never the logo or the look of a real app. The card springs down from above the
|
|
5
|
+
screen, holds, and slides back up at the end (the template draws its own exit, so `exit` is "none").
|
|
6
|
+
|
|
7
|
+
A notification shows only what really happened in the person's material (a message, a sale, a pix): `notes_pt`
|
|
8
|
+
says so; the template cannot check it.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
|
|
16
|
+
from motion import engine as E
|
|
17
|
+
from motion.engine import MotionError
|
|
18
|
+
|
|
19
|
+
TEMPLATE = {
|
|
20
|
+
"id": "notificacao",
|
|
21
|
+
"label_pt": "Notificação de celular",
|
|
22
|
+
"description_pt": "Um cartão de notificação de celular que desce do alto da tela com um balanço leve, fica "
|
|
23
|
+
"parado para leitura e sobe de novo no fim. Ícone genérico, sem marca de app real.",
|
|
24
|
+
"use_when_pt": "Quando a fala cita uma mensagem, um lembrete, um aviso ou uma venda: pergunta de cliente, "
|
|
25
|
+
"aula liberada, lembrete de live, pedido de ajuda, venda que caiu.",
|
|
26
|
+
"notes_pt": "Mostre só o que aconteceu de verdade no material da pessoa: não invente venda, pix, valor, "
|
|
27
|
+
"mensagem nem depoimento. Também não imite nome, ícone ou cor de app real "
|
|
28
|
+
"(banco, WhatsApp, Hotmart, Kiwify): o ícone daqui é genérico de propósito.",
|
|
29
|
+
"category": "chamada",
|
|
30
|
+
"version": 1,
|
|
31
|
+
"params": {
|
|
32
|
+
"app_name": {"type": "text", "default": "Mensagens", "max_len": 24,
|
|
33
|
+
"label_pt": "Nome do app no topo do cartão (genérico, ex.: Mensagens, Lembretes, Agenda)"},
|
|
34
|
+
"title": {"type": "text", "default": "Nova mensagem", "max_len": 48, "required_text": True,
|
|
35
|
+
"label_pt": "Título em negrito (ex.: nome de quem mandou)"},
|
|
36
|
+
"text": {"type": "text", "default": "Oi! Vi seu vídeo e quero saber como funciona.", "max_len": 120,
|
|
37
|
+
"label_pt": "Texto da notificação (até 3 linhas; vazio: só o título)"},
|
|
38
|
+
"time_label": {"type": "text", "default": "agora", "max_len": 12,
|
|
39
|
+
"label_pt": "Hora no canto (ex.: agora, 2 min, 09:41)"},
|
|
40
|
+
"icon": {"type": "choice", "choices": ["mensagem", "sino", "check", "coracao", "letra"],
|
|
41
|
+
"default": "mensagem",
|
|
42
|
+
"label_pt": "Ícone genérico: balão de mensagem, sino, check, coração ou a primeira letra do app"},
|
|
43
|
+
"exit_mode": {"type": "choice", "choices": ["subir", "apagar"], "default": "subir",
|
|
44
|
+
"label_pt": "Saída no fim: sobe de volta para fora da tela ou apaga no lugar"},
|
|
45
|
+
"stack": {"type": "bool", "default": False,
|
|
46
|
+
"label_pt": "Mostrar outra notificação atrás (parece que chegaram várias)"},
|
|
47
|
+
},
|
|
48
|
+
"default_duration": 3.5,
|
|
49
|
+
"default_region": "faixa_topo",
|
|
50
|
+
"sfx": [{"at": 0.0, "name": "notification", "why": "notificação chegando"}],
|
|
51
|
+
"exit": "none",
|
|
52
|
+
"bleed": 260,
|
|
53
|
+
"movable": False,
|
|
54
|
+
"example": {"app_name": "Mensagens", "title": "Juliana",
|
|
55
|
+
"text": "Comecei ontem e já entendi o passo a passo. Posso tirar uma dúvida?"},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
LAND = 0.80 # the spring is forced to rest exactly here
|
|
59
|
+
DAMPING, FREQ = 0.68, 1.9
|
|
60
|
+
EXIT_T = 0.36
|
|
61
|
+
EXIT_DIP = 0.18 # share of the exit spent on a small dip before the flick up
|
|
62
|
+
PAD = 32.0
|
|
63
|
+
ICON = 100.0
|
|
64
|
+
PEEK = 44.0 # design px of the card already on screen on frame 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------ layout
|
|
68
|
+
|
|
69
|
+
def validate(params: dict) -> None:
|
|
70
|
+
"""The title needs letters the font draws (refused when the item is planned)."""
|
|
71
|
+
if not E.clean_text(params["title"], "text_bold").strip():
|
|
72
|
+
raise MotionError("notificacao.params.title precisa de letras ou números que a fonte desenha")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _truncate(text: str, font_key: str, size: float, max_width: float) -> str:
|
|
76
|
+
if E.measure(text, font_key, size).width <= max_width:
|
|
77
|
+
return text
|
|
78
|
+
words = text
|
|
79
|
+
while words and E.measure(words.rstrip() + "...", font_key, size).width > max_width:
|
|
80
|
+
words = words[:-1]
|
|
81
|
+
return words.rstrip(" ,.;:") + "..."
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@lru_cache(maxsize=64)
|
|
85
|
+
def _layout(params_json: str, box: E.Box) -> dict:
|
|
86
|
+
p = json.loads(params_json)
|
|
87
|
+
W = box.w
|
|
88
|
+
content_x = PAD + ICON + 26.0
|
|
89
|
+
content_w = W - content_x - PAD
|
|
90
|
+
app = E.clean_text(p["app_name"], "text_semibold").strip()
|
|
91
|
+
time_label = E.clean_text(p["time_label"], "text_semibold").strip()
|
|
92
|
+
title = E.clean_text(p["title"], "text_bold").strip()
|
|
93
|
+
body = E.clean_text(p["text"], "text_semibold").strip()
|
|
94
|
+
if not title:
|
|
95
|
+
raise MotionError("notificacao.params.title precisa de letras ou números que a fonte desenha")
|
|
96
|
+
|
|
97
|
+
head_size = 30.0
|
|
98
|
+
head_cap = E.measure("H", "text_semibold", head_size).cap
|
|
99
|
+
time_w = E.measure(time_label, "text_semibold", head_size).width if time_label else 0.0
|
|
100
|
+
app_text = _truncate(app.upper(), "text_semibold", head_size, content_w - time_w - 24.0) if app else ""
|
|
101
|
+
|
|
102
|
+
title_size = E.fit_text(title, "text_bold", content_w, 44.0, 34.0)
|
|
103
|
+
title = _truncate(title, "text_bold", title_size, content_w)
|
|
104
|
+
title_cap = E.measure("H", "text_bold", title_size).cap
|
|
105
|
+
|
|
106
|
+
body_size = 40.0
|
|
107
|
+
lines: list[str] = []
|
|
108
|
+
if body:
|
|
109
|
+
lines = E.wrap_text(body, "text_semibold", body_size, content_w, 3)
|
|
110
|
+
while body_size > 32.0 and (len(lines) > 3 or any(
|
|
111
|
+
E.measure(ln, "text_semibold", body_size).width > content_w + 0.5 for ln in lines)):
|
|
112
|
+
body_size = max(32.0, body_size * 0.95)
|
|
113
|
+
lines = E.wrap_text(body, "text_semibold", body_size, content_w, 3)
|
|
114
|
+
lines = [_truncate(ln, "text_semibold", body_size, content_w) for ln in lines[:3]]
|
|
115
|
+
body_cap = E.measure("H", "text_semibold", body_size).cap
|
|
116
|
+
pitch = body_size * 1.26
|
|
117
|
+
|
|
118
|
+
head_base = PAD + head_cap + 2.0
|
|
119
|
+
title_base = head_base + 18.0 + title_cap
|
|
120
|
+
body_base = title_base + 14.0 + body_cap
|
|
121
|
+
bottom = (body_base + pitch * (len(lines) - 1) + body_size * 0.28) if lines else title_base + title_size * 0.28
|
|
122
|
+
card_h = max(ICON + 2 * PAD, bottom + PAD)
|
|
123
|
+
extra = (card_h - (bottom + PAD)) / 2.0 # a short text sits in the middle of the icon's height
|
|
124
|
+
head_base, title_base, body_base = head_base + extra, title_base + extra, body_base + extra
|
|
125
|
+
top = min(12.0, max(0.0, box.h - card_h))
|
|
126
|
+
|
|
127
|
+
# slide distance: from above the screen (only the bottom PEEK px show on frame 0) when the canvas reaches
|
|
128
|
+
# it, else a short slide with a fade
|
|
129
|
+
canvas_top = box.y - box.bleed
|
|
130
|
+
off_screen = canvas_top <= -20.0
|
|
131
|
+
if off_screen:
|
|
132
|
+
a0 = _arrive(1.0 / box.fps)
|
|
133
|
+
travel = max(150.0, (box.y + top + card_h - PEEK) / max(0.2, 1.0 - a0))
|
|
134
|
+
travel = min(travel, box.bleed + box.y + top - 10.0 + card_h)
|
|
135
|
+
else:
|
|
136
|
+
travel = 150.0
|
|
137
|
+
escuro = box.theme.dark
|
|
138
|
+
acc = box.theme.accent
|
|
139
|
+
return {
|
|
140
|
+
"W": W, "top": top, "card_h": card_h, "content_x": content_x, "content_w": content_w,
|
|
141
|
+
"app": app_text, "time": time_label, "time_w": time_w, "head_size": head_size, "head_base": head_base,
|
|
142
|
+
"title": title, "title_size": title_size, "title_base": title_base,
|
|
143
|
+
"lines": lines, "body_size": body_size, "body_base": body_base, "pitch": pitch,
|
|
144
|
+
"travel": travel, "off_screen": off_screen, "letter": (app[:1] or title[:1]).upper(),
|
|
145
|
+
"grad": (E.mix_color(acc, (255, 255, 255), 0.18), E.mix_color(acc, (0, 0, 0), 0.22)),
|
|
146
|
+
"fill": (38, 38, 42, 236) if escuro else (250, 250, 250, 244),
|
|
147
|
+
"stack_fill": (30, 30, 34, 200) if escuro else (236, 236, 236, 220),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------ icon glyphs (generic)
|
|
152
|
+
|
|
153
|
+
def _icon(cv: E.Canvas, L: dict, x: float, y: float, kind: str) -> None:
|
|
154
|
+
s = ICON
|
|
155
|
+
light, dark = L["grad"]
|
|
156
|
+
cv.rect(x, y, s, s, radius=s * 0.24, fill=E.Gradient(light, dark, (x, y), (x + s, y + s)),
|
|
157
|
+
shadow=E.Shadow(blur=10, dy=4, opacity=0.25))
|
|
158
|
+
cx, cy = x + s / 2.0, y + s / 2.0
|
|
159
|
+
ink = "on_accent"
|
|
160
|
+
if kind == "mensagem":
|
|
161
|
+
bw, bh = s * 0.58, s * 0.44
|
|
162
|
+
bx, by = cx - bw / 2.0, cy - bh / 2.0 - s * 0.04
|
|
163
|
+
cv.rect(bx, by, bw, bh, radius=bh * 0.42, fill=ink)
|
|
164
|
+
cv.polygon([(bx + bw * 0.22, by + bh - 2.0), (bx + bw * 0.46, by + bh - 2.0),
|
|
165
|
+
(bx + bw * 0.14, by + bh + s * 0.13)], fill=ink)
|
|
166
|
+
for k in (-1, 0, 1):
|
|
167
|
+
cv.circle(cx + k * s * 0.12, by + bh / 2.0, s * 0.036, fill=light)
|
|
168
|
+
elif kind == "sino":
|
|
169
|
+
cv.circle(cx, cy - s * 0.30, s * 0.05, fill=ink)
|
|
170
|
+
body = [(cx - s * 0.13, cy - s * 0.20), (cx + s * 0.13, cy - s * 0.20),
|
|
171
|
+
(cx + s * 0.22, cy + s * 0.14), (cx - s * 0.22, cy + s * 0.14)]
|
|
172
|
+
cv.polygon(body, fill=ink, radius=s * 0.04)
|
|
173
|
+
cv.circle(cx, cy - s * 0.13, s * 0.14, fill=ink)
|
|
174
|
+
cv.pill(cx - s * 0.29, cy + s * 0.10, s * 0.58, s * 0.09, fill=ink)
|
|
175
|
+
cv.circle(cx, cy + s * 0.25, s * 0.065, fill=ink)
|
|
176
|
+
elif kind == "check":
|
|
177
|
+
cv.check(cx, cy + s * 0.02, s * 0.52, width=s * 0.11, color=ink)
|
|
178
|
+
elif kind == "coracao":
|
|
179
|
+
cv.polygon(E.heart_points(cx, cy + s * 0.02, s * 0.56, n=48, closed=False), fill=ink)
|
|
180
|
+
else:
|
|
181
|
+
cv.text(cx, cy, L["letter"], "sans_black", s * 0.50, color=ink, anchor="mc")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ------------------------------------------------------------------ motion
|
|
185
|
+
|
|
186
|
+
def _arrive(t_in: float) -> float:
|
|
187
|
+
"""0 -> 1 with a light overshoot; exactly 1 from LAND on (no endless sub-pixel wobble)."""
|
|
188
|
+
if t_in >= LAND:
|
|
189
|
+
return 1.0
|
|
190
|
+
v = E.spring(t_in, DAMPING, FREQ)
|
|
191
|
+
blend = E.eased(t_in, LAND - 0.25, 0.25, "in_out_sine")
|
|
192
|
+
return v + (1.0 - v) * blend
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def render(frame_index: int, fps: int, params: dict, box: E.Box) -> E.Canvas:
|
|
196
|
+
key = json.dumps(params, sort_keys=True)
|
|
197
|
+
L = _layout(key, box)
|
|
198
|
+
cv = E.Canvas(box)
|
|
199
|
+
t = frame_index / fps
|
|
200
|
+
t_in = t + 1.0 / fps
|
|
201
|
+
end = box.frames / fps
|
|
202
|
+
|
|
203
|
+
a = _arrive(t_in)
|
|
204
|
+
dy = -L["travel"] * (1.0 - a)
|
|
205
|
+
opacity = E.eased(t_in, 0.0, 0.10 if L["off_screen"] else 0.25, "out_quad")
|
|
206
|
+
scale = 0.96 + 0.04 * a
|
|
207
|
+
|
|
208
|
+
# exit, drawn by the template: up and out, or a fade in place
|
|
209
|
+
t_exit = end - EXIT_T
|
|
210
|
+
if t >= t_exit - 1e-9:
|
|
211
|
+
# the last frame is fully gone (+ 1 frame so the exit ends on the last frame)
|
|
212
|
+
p = E.progress(t + 1.0 / fps, t_exit, EXIT_T)
|
|
213
|
+
if params["exit_mode"] == "subir":
|
|
214
|
+
dy += 12.0 * E.pulse(p, 0.0, EXIT_DIP * 2.0)
|
|
215
|
+
dy -= (L["travel"] + PEEK + 60.0) * E.in_quad(E.remap(p, EXIT_DIP, 1.0))
|
|
216
|
+
if not L["off_screen"]:
|
|
217
|
+
opacity *= 1.0 - E.eased(p, 0.3, 0.7, "in_quad")
|
|
218
|
+
else:
|
|
219
|
+
opacity *= 1.0 - E.out_quad(p)
|
|
220
|
+
scale *= 1.0 - 0.04 * p
|
|
221
|
+
if p >= 1.0:
|
|
222
|
+
return cv
|
|
223
|
+
|
|
224
|
+
if t_in >= LAND and t < t_exit - 1e-9:
|
|
225
|
+
# resting: every frame is the same picture (settle() cannot be used with a template-drawn exit)
|
|
226
|
+
cv.image.alpha_composite(_rest_image(key, box))
|
|
227
|
+
return cv
|
|
228
|
+
_draw_card(cv, L, params, dy, scale, opacity)
|
|
229
|
+
return cv
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@lru_cache(maxsize=8)
|
|
233
|
+
def _rest_image(params_json: str, box: E.Box):
|
|
234
|
+
cv = E.Canvas(box)
|
|
235
|
+
_draw_card(cv, _layout(params_json, box), json.loads(params_json), 0.0, 1.0, 1.0)
|
|
236
|
+
return cv.image
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _draw_card(cv: E.Canvas, L: dict, params: dict, dy: float, scale: float, opacity: float) -> None:
|
|
240
|
+
x, y, w, h = 0.0, L["top"], L["W"], L["card_h"]
|
|
241
|
+
with cv.group(dy=dy, scale=scale, pivot=(w / 2.0, y), opacity=opacity):
|
|
242
|
+
if params["stack"]:
|
|
243
|
+
cv.rect(w * 0.05, y + 22.0, w * 0.90, h, radius=40.0, fill=L["stack_fill"],
|
|
244
|
+
shadow=E.Shadow(blur=20, dy=10, opacity=0.25), stroke="card_border", stroke_width=2.0)
|
|
245
|
+
cv.rect(x, y, w, h, radius=44.0, fill=L["fill"], shadow=E.Shadow(blur=34, dy=16, opacity=0.38),
|
|
246
|
+
stroke="card_border", stroke_width=2.0)
|
|
247
|
+
_icon(cv, L, PAD, y + PAD, params["icon"])
|
|
248
|
+
cx = L["content_x"]
|
|
249
|
+
if L["app"]:
|
|
250
|
+
cv.text(cx, y + L["head_base"], L["app"], "text_semibold", L["head_size"], color="muted", tracking=1.5)
|
|
251
|
+
if L["time"]:
|
|
252
|
+
cv.text(w - PAD, y + L["head_base"], L["time"], "text_semibold", L["head_size"], color="muted",
|
|
253
|
+
anchor="rs")
|
|
254
|
+
cv.text(cx, y + L["title_base"], L["title"], "text_bold", L["title_size"], color="text")
|
|
255
|
+
for i, line in enumerate(L["lines"]):
|
|
256
|
+
cv.text(cx, y + L["body_base"] + i * L["pitch"], line, "text_semibold", L["body_size"], color="text",
|
|
257
|
+
opacity=0.9)
|
|
258
|
+
|
|
259
|
+
def sfx(params: dict, fps: int, frames: int) -> list[dict]:
|
|
260
|
+
return [{"frame": 0, "name": "notification", "volume": 1.0, "why": "notificação chegando"}]
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def suggested_duration(params: dict) -> float:
|
|
264
|
+
"""Arrival, reading time by the size of the text, and the exit."""
|
|
265
|
+
words = len((params["title"] + " " + params["text"]).split())
|
|
266
|
+
return round(min(8.0, max(TEMPLATE["default_duration"], LAND + 0.8 + words * 0.22 + EXIT_T)), 2)
|