primocode 8.38.1 → 8.40.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.
@@ -0,0 +1,135 @@
1
+ {
2
+ "id": "tela-motion-d6150",
3
+ "ferramenta": "tela",
4
+ "titulo": "motion",
5
+ "doc": {
6
+ "tema": "meia-noite",
7
+ "largura": 1920,
8
+ "altura": 1080,
9
+ "cenas": [
10
+ {
11
+ "layout": "livre",
12
+ "duracao": 6,
13
+ "fundo": "aurora",
14
+ "elementos": [
15
+ {
16
+ "tipo": "forma",
17
+ "forma": "circulo",
18
+ "x": 1250,
19
+ "y": 120,
20
+ "l": 520,
21
+ "a": 520,
22
+ "preenchimento": "#ff5a36",
23
+ "opacidade": 0.9
24
+ },
25
+ {
26
+ "tipo": "forma",
27
+ "forma": "retangulo",
28
+ "material": "vidro",
29
+ "x": 120,
30
+ "y": 140,
31
+ "l": 860,
32
+ "a": 420,
33
+ "raio": 36,
34
+ "preenchimento": "#22d3ee"
35
+ },
36
+ {
37
+ "tipo": "texto",
38
+ "x": 170,
39
+ "y": 190,
40
+ "l": 760,
41
+ "a": 120,
42
+ "texto": "Liquid glass de verdade",
43
+ "tamanho": 74,
44
+ "peso": 750
45
+ },
46
+ {
47
+ "tipo": "texto",
48
+ "x": 170,
49
+ "y": 330,
50
+ "l": 760,
51
+ "a": 60,
52
+ "texto": "desfoca o que está atrás, bisel em cima",
53
+ "tamanho": 34,
54
+ "cor": "#bfe9f4"
55
+ },
56
+ {
57
+ "tipo": "texto",
58
+ "material": "vidro",
59
+ "x": 170,
60
+ "y": 430,
61
+ "l": 420,
62
+ "a": 70,
63
+ "texto": "pílula de vidro",
64
+ "tamanho": 32
65
+ },
66
+ {
67
+ "tipo": "icone",
68
+ "nome": "foguete",
69
+ "estilo": "app",
70
+ "x": 1130,
71
+ "y": 640,
72
+ "tamanho": 220,
73
+ "fundo": "#8b5cf6",
74
+ "som": "pop"
75
+ },
76
+ {
77
+ "tipo": "icone",
78
+ "nome": "raio",
79
+ "estilo": "vidro",
80
+ "x": 1400,
81
+ "y": 640,
82
+ "tamanho": 220,
83
+ "cor": "#ffb03a"
84
+ },
85
+ {
86
+ "tipo": "objeto3d",
87
+ "forma": "cubo",
88
+ "x": 620,
89
+ "y": 620,
90
+ "tamanho": 260,
91
+ "girar": 0,
92
+ "faces": [
93
+ "IA",
94
+ "3D",
95
+ "Motion",
96
+ "Vidro",
97
+ "Som",
98
+ "Marca"
99
+ ]
100
+ },
101
+ {
102
+ "tipo": "texto",
103
+ "x": 120,
104
+ "y": 700,
105
+ "l": 400,
106
+ "a": 100,
107
+ "texto": "chaves 3D",
108
+ "tamanho": 60,
109
+ "peso": 700,
110
+ "chaves": [
111
+ {
112
+ "t": 0,
113
+ "giroY": -60,
114
+ "opacidade": 0,
115
+ "x": -200
116
+ },
117
+ {
118
+ "t": 0.4,
119
+ "giroY": 0,
120
+ "opacidade": 1,
121
+ "x": 0
122
+ },
123
+ {
124
+ "t": 1,
125
+ "giroY": 20
126
+ }
127
+ ],
128
+ "suavizar": "elastico"
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ },
134
+ "atualizado": "2026-09-09T12:31:01+00:00"
135
+ }
package/studio/edge.py CHANGED
@@ -117,32 +117,81 @@ def disponivel():
117
117
  return _verificado
118
118
 
119
119
 
120
+ # Por que a instalação falhava calada, e o vídeo saía com a voz do Google:
121
+ # `pip install --user` é recusado pelo Python do sistema em Debian/Ubuntu e
122
+ # no do Homebrew ("externally-managed-environment", PEP 668), e em máquina
123
+ # sem pip o `-m pip` nem existe. Um `pip install` só, em segundo plano, com a
124
+ # saída jogada fora, era exatamente a falha que ninguém vê. Agora são várias
125
+ # tentativas, em ordem, e a última linha do erro de cada uma fica guardada
126
+ # para o status contar o que houve.
127
+ ESTRATEGIAS = [
128
+ ["-m", "pip", "install", "--user", "--quiet", "edge-tts"],
129
+ ["-m", "pip", "install", "--user", "--quiet", "--break-system-packages", "edge-tts"],
130
+ ["-m", "pip", "install", "--quiet", "edge-tts"],
131
+ ["-m", "pip", "install", "--quiet", "--break-system-packages", "edge-tts"],
132
+ ]
133
+ _ultimo_erro = ""
134
+ _thread = None
135
+
136
+
120
137
  def _rotina_instalar():
121
- global _instalando, _verificado
138
+ global _instalando, _verificado, _ultimo_erro
122
139
  try:
123
- subprocess.run(
124
- [sys.executable, "-m", "pip", "install", "--user", "--quiet", "edge-tts"],
125
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=180)
126
- except Exception as e:
127
- sys.stderr.write("[edge] pip install falhou: %s\n" % e)
140
+ # Sem pip, primeiro o ensurepip — é o que o python.org e a Apple
141
+ # trazem para o caso.
142
+ try:
143
+ r = subprocess.run([sys.executable, "-m", "pip", "--version"],
144
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30)
145
+ if r.returncode != 0:
146
+ subprocess.run([sys.executable, "-m", "ensurepip", "--user", "--default-pip"],
147
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=120)
148
+ except Exception:
149
+ pass
150
+ for args in ESTRATEGIAS:
151
+ try:
152
+ r = subprocess.run([sys.executable] + args, stdout=subprocess.DEVNULL,
153
+ stderr=subprocess.PIPE, timeout=240)
154
+ except Exception as e:
155
+ _ultimo_erro = str(e)[-200:]
156
+ continue
157
+ _verificado = None
158
+ if r.returncode == 0 and disponivel():
159
+ _ultimo_erro = ""
160
+ sys.stderr.write("[edge] edge-tts instalado (%s)\n" % " ".join(args[2:]))
161
+ return
162
+ _ultimo_erro = (r.stderr or b"").decode("utf-8", "replace").strip().splitlines()[-1:][0][-200:] \
163
+ if (r.stderr or b"").strip() else "pip devolveu %s" % r.returncode
164
+ sys.stderr.write("[edge] não consegui instalar o edge-tts: %s\n" % _ultimo_erro)
128
165
  finally:
129
166
  _verificado = None # força nova checagem
130
167
  with _tranca:
131
168
  _instalando = False
132
169
 
133
170
 
134
- def instalar():
135
- """Tenta trazer o pacote em segundo plano, uma vez. Não bloqueia: quem
136
- pediu narração agora ouve o próximo motor da corrente, e a partir da
137
- próxima vez o Edge já responde."""
138
- global _instalando
171
+ def instalar(esperar=0):
172
+ """Traz o pacote — em segundo plano por padrão; com `esperar` (segundos),
173
+ quem chamou fica aguardando até esse teto.
174
+
175
+ Esperar existe porque a narração de um vídeo NÃO pode sair pela voz de
176
+ reserva só porque o pip ainda estava rodando: quem pede voz agora e tem
177
+ internet merece a voz neural agora. Meio minuto de pip é menos ruim que
178
+ um vídeo inteiro com voz de robô."""
179
+ global _instalando, _thread
139
180
  if disponivel():
140
- return
181
+ return True
141
182
  with _tranca:
142
- if _instalando:
143
- return
144
- _instalando = True
145
- threading.Thread(target=_rotina_instalar, daemon=True).start()
183
+ if not _instalando:
184
+ _instalando = True
185
+ _thread = threading.Thread(target=_rotina_instalar, daemon=True)
186
+ _thread.start()
187
+ t = _thread
188
+ if t and esperar:
189
+ t.join(esperar)
190
+ return disponivel()
191
+
192
+
193
+ def ultimo_erro():
194
+ return _ultimo_erro
146
195
 
147
196
 
148
197
  def _dur_do_vtt(caminho):
@@ -215,6 +264,7 @@ def estado():
215
264
  "motor": "edge",
216
265
  "disponivel": disponivel(),
217
266
  "instalando": inst,
267
+ "erro": _ultimo_erro or None,
218
268
  "precisa_internet": True,
219
269
  "vozes": [{"id": v["id"], "nome": v["nome"], "genero": v["genero"],
220
270
  "padrao": bool(v.get("padrao"))} for v in CATALOGO],
@@ -89,12 +89,22 @@ um elemento flutuando por cima do layout.
89
89
  // path SVG numa caixa 24×24 (string ou lista de strings), no traço da casa
90
90
  { "tipo":"icone", "nome":"raio", "tamanho":120, "cor":"#ffb03a" }
91
91
  { "tipo":"icone", "caminho":"M3 6h18l-2 10H5zM8 20h.01M16 20h.01", "tamanho":96 }
92
+ // como OBJETO: estilo "app" (azulejo de aplicativo) ou "vidro" (liquid glass)
93
+ { "tipo":"icone", "nome":"foguete", "estilo":"app", "tamanho":200, "fundo":"#8b5cf6" }
92
94
 
93
95
  // FORMA
94
96
  { "tipo":"forma", "forma":"retangulo", "x":200,"y":200,"l":400,"a":220,
95
97
  "preenchimento":"#ff5a36", "raio":24, "rotacao":-4, "opacidade":0.9 }
96
98
  // forma: retangulo | circulo | triangulo | linha | seta
97
99
 
100
+ // MATERIAL — em qualquer elemento: "vidro" é o liquid glass (desfoca o que
101
+ // está atrás, bisel de luz, reflexo). Na forma, preenchimento vira a tinta.
102
+ { "tipo":"forma", "forma":"retangulo", "material":"vidro", "raio":32, "x":100,"y":100,"l":800,"a":400 }
103
+
104
+ // SOM — no elemento, toca quando ele entra: whoosh | impacto | click | pop |
105
+ // ding | subida | tique | brilho, ou {"arquivo":"/midia/x.mp3","volume":0.8}
106
+ { "tipo":"numeros", "itens":["+38% | em 12 meses"], "contar":true, "som":"ding" }
107
+
98
108
  // CÓDIGO
99
109
  { "tipo":"codigo", "texto":"npm install primocode" }
100
110
 
@@ -186,8 +196,16 @@ completa em segundos (`0` deixa parado no `angulo` dado), `inclinar` levanta
186
196
  ou baixa a câmera, `cores` pinta face a face.
187
197
 
188
198
  Aceita `chaves` como qualquer elemento: dá para o cubo atravessar a cena
189
- girando. Use **um** por cena — dois objetos girando disputam o olho. Uma cena com tema diferente no meio cria respiro —
190
- use uma vez, não cinco.
199
+ girando. Use **um** por cena — dois objetos girando disputam o olho.
200
+
201
+ ### Chaves de animação (motion)
202
+
203
+ `chaves` em qualquer elemento: instantes de `t` 0 a 1 com `x`, `y`
204
+ (deslocamento), `escala`, `escalaX`, `escalaY`, `giro`, **`giroX`, `giroY`,
205
+ `prof`** (3D: vira em profundidade, aproxima da câmera), `opacidade`,
206
+ `desfoque`. No elemento: `suavizar` (`suave` · `linear` · `entrada` · `saida`
207
+ · `elastico` · `salto` · `degrau`), `repetir`, `vaiEVolta`. A spec do Corte
208
+ tem os exemplos.
191
209
 
192
210
  ### Mídia do computador
193
211
 
@@ -181,40 +181,63 @@ narração, e uma disputa com quem fala em qualquer outro caso.
181
181
  `src` vem de `POST /api/midia` (upload) ou é uma URL direta de áudio.
182
182
  `inicio` pula os primeiros segundos da faixa.
183
183
 
184
- **Não baixe áudio de YouTube, Spotify ou similares** — quebra os termos dessas
185
- plataformas e o direito autoral da faixa. Use música do próprio usuário, faixa
186
- livre de direitos, ou uma das trilhas geradas acima.
184
+ ### Música da internet, com licença (`buscar_audio`)
187
185
 
188
- ---
186
+ Uma faixa de verdade, tocada por gente, sem o usuário ter arquivo nenhum:
187
+ `buscar_audio` procura no Openverse (Jamendo, Freesound, Wikimedia — licenças
188
+ Creative Commons que permitem uso, inclusive comercial), **baixa** a melhor e
189
+ devolve a URL `/midia/…` pronta:
189
190
 
190
- ## Studio ou Remotion?
191
+ ```
192
+ buscar_audio { "consulta": "upbeat corporate", "duracao": 30 }
193
+ → url "/midia/corporate-succes-8c2911.mp3", licenca "CC BY-SA",
194
+ credito "Corporate Succes — Arkadii Kaplan (CC BY-SA 3.0, via jamendo)"
195
+ ```
191
196
 
192
- **O padrão é o Studio.** Ele exporta o MP4 num clique, em segundos, sem
193
- instalar nada. Não ofereça Remotion sem motivo — trocar um clique por
194
- `npm install` de 250 MB é piorar a vida de quem só quer o vídeo.
197
+ ```json
198
+ "trilha": { "tipo": "arquivo", "src": "/midia/corporate-succes-8c2911.mp3", "volume": 0.55, "repetir": true }
199
+ ```
195
200
 
196
- Chame `remotion_projeto` (com o `id` da peça) em três casos, e só neles:
201
+ Consulta **em inglês**, pelo clima: `upbeat corporate`, `calm piano`, `epic
202
+ cinematic`, `lofi chill`, `acoustic warm`. `duracao` é a do vídeo, para a faixa
203
+ combinar. Para efeitos, `"tipo": "efeito"` (`whoosh`, `click`, `ding`,
204
+ `swoosh`, `pop`) — a URL vai em `som: {"arquivo": …}`.
197
205
 
198
- 1. **O usuário pediu Remotion pelo nome.** Não discuta, faça.
199
- 2. **A peça precisa do que o Studio não dá:** render determinístico (o mesmo
200
- arquivo em qualquer máquina, sem depender de a aba estar em foco), vídeo
201
- muito longo, fps ou codec específico (ProRes, canal alfa), renderização em
202
- servidor.
203
- 3. **O usuário quer mexer no código.** O projeto que nasce é React, é dele, e
204
- entra no git.
206
+ **O crédito faz parte da peça.** Licença `CC BY`/`BY-SA` pede o crédito: ponha
207
+ a linha `credito` da resposta na assinatura ou numa legenda pequena da última
208
+ cena. `CC0` e domínio público não pedem nada. A resposta diz qual é o caso.
209
+ Quando a busca não acha nada, use a trilha **gerada** — nunca um "parecido"
210
+ de YouTube.
205
211
 
206
- A peça é a MESMA — `remotion_projeto` parte do documento que você já criou
207
- com `studio_criar`. Não existe um segundo formato para aprender: crie a peça
208
- normalmente, veja no navegador, e só então gere o projeto se fizer sentido.
212
+ **Não baixe áudio de YouTube, Spotify ou similares** — quebra os termos dessas
213
+ plataformas e o direito autoral da faixa. Use `buscar_audio`, música do próprio
214
+ usuário ou uma das trilhas geradas acima.
209
215
 
210
- O projeto nasce sem `node_modules`. **Não rode `renderizar: true` antes de o
211
- `npm install` ter sido feito** — diga ao usuário os dois comandos e deixe ele
212
- decidir se quer esperar.
216
+ ---
213
217
 
214
- **Licença:** Remotion é grátis para pessoa física e empresa de até três
215
- pessoas; acima disso a empresa precisa de licença paga. Avise ao oferecer —
216
- descobrir isso depois de montar o fluxo de trabalho em cima seria péssimo.
217
- Isso vale para o projeto do usuário, não para o PrimoCode.
218
+ ## Studio E Remotion — os dois, mesclados
219
+
220
+ **Não é uma escolha.** O Studio é quem AUTORA e mostra: a cena, o motion, a
221
+ narração, a trilha gerada, os efeitos — o link "ver" é o vídeo, na hora. Quando
222
+ o usuário pede o ARQUIVO (`studio_exportar`), o PrimoCode renderiza a mesma
223
+ peça quadro a quadro pelo **Remotion** (instalado uma vez, sozinho, em
224
+ `~/.primocode/remotion`) — determinístico, sem perder quadro, com todas as
225
+ animações, chaves 3D, contagem e clipes no instante certo — e **mistura o
226
+ áudio do Studio** (narração, trilha, efeitos sintetizados) no MP4 final. Você
227
+ não escolhe motor: chama `studio_exportar` e recebe o arquivo.
228
+
229
+ O primeiro vídeo que saiu só do Remotion estava **mudo e parado** — animações
230
+ desligadas, só `trilha.src` como som. Foi por isso que a mescla existe: o
231
+ Remotion sozinho não tem a narração nem a trilha gerada; o Studio sozinho
232
+ grava em tempo real. Juntos, o arquivo sai como a prévia.
233
+
234
+ `remotion_projeto` (com o `id` da peça) é OUTRA coisa: gera o **projeto React**
235
+ na pasta do usuário, com as cenas, a mídia e o motion, para quem quer mexer no
236
+ código, versionar no git ou renderizar em servidor. Só quando o usuário pedir
237
+ isso — não é o jeito de gerar o arquivo. O projeto nasce sem `node_modules`;
238
+ diga os dois comandos (`npm install`, `npm run render`) e deixe ele decidir. A
239
+ trilha **gerada** e os sons **sintetizados** não existem no projeto solto
240
+ (são WebAudio ao vivo): lá, use `buscar_audio` para ter a trilha em arquivo.
218
241
 
219
242
  ---
220
243
 
@@ -242,6 +265,15 @@ número, botão, detalhe, o cartão — e libera o fundo `"marca"`:
242
265
  "tema": "claro"
243
266
  ```
244
267
 
268
+ **De onde vêm a cor e a logo: do site da empresa.** Antes de escrever a peça,
269
+ chame `marca_da_empresa { "nome": "PicPay" }` (ou `"site": "https://…"`). Ela
270
+ lê o site, baixa a logo para o estúdio e devolve `marca: {cor, logo}` pronta
271
+ para colar, mais a paleta, as fontes e a frase da empresa. Não chute cor de
272
+ marca nem peça o arquivo ao usuário antes de tentar isso — a logo está no
273
+ site. Só quando a tool não achar (site montado por JavaScript, sem logo
274
+ baixável) é que se pede o arquivo (`studio_midia`) ou se assina com o nome em
275
+ texto.
276
+
245
277
  Tema CLARO na maioria dos anúncios de marca. O escuro é para a cena de
246
278
  produto e para a vitrine; o resto da peça respira no branco.
247
279
 
@@ -423,11 +455,38 @@ cena e a série ganha movimento sem esforço. `opcoes` marca a alternativa
423
455
  `certa` com ✓ (não escreva o ✓ no texto: ele vem sozinho). Alternativa faz o
424
456
  espectador pensar antes da resposta, e pensar é o que segura alguém no vídeo.
425
457
 
426
- ### Vidro
458
+ ### Vidro — o liquid glass, em qualquer elemento
427
459
 
428
- `"estilo": "vidro"` em `cartoes` dá painel translúcido com bisel de luz em
429
- cima e sombra embaixo — o "liquid glass". Funciona em tema escuro; em tema
430
- claro use os cartões normais.
460
+ `"material": "vidro"` em **qualquer** elemento: texto vira pílula de vidro,
461
+ `forma` vira painel, `imagem` ganha moldura, `lista`/`cartoes`/`numeros`
462
+ ganham fundo. É vidro de verdade: **desfoca o que está atrás** (o fundo
463
+ `aurora`, a foto, o círculo colorido), tem o bisel de luz em cima e a sombra
464
+ embaixo, e o reflexo diagonal atravessa devagar durante a cena.
465
+
466
+ ```json
467
+ { "tipo": "forma", "forma": "retangulo", "material": "vidro", "raio": 36,
468
+ "x": 120, "y": 140, "l": 860, "a": 420, "preenchimento": "#22d3ee" }
469
+ { "tipo": "texto", "material": "vidro", "texto": "pílula de vidro", "tamanho": 32 }
470
+ ```
471
+
472
+ Na forma, `preenchimento` vira a **tinta** do vidro (fraca) — não pinta o
473
+ fundo. `raio` arredonda (padrão 28). **Ponha algo atrás**: vidro sobre fundo
474
+ liso é só um retângulo claro; sobre `aurora`, `brilho`, uma foto ou uma forma
475
+ colorida, é o efeito. `"estilo": "vidro"` em `cartoes` continua valendo.
476
+
477
+ ### Ícone como objeto (`estilo: "app"` / `"vidro"`)
478
+
479
+ ```json
480
+ { "tipo": "icone", "nome": "foguete", "estilo": "app", "tamanho": 220, "fundo": "#8b5cf6" }
481
+ { "tipo": "icone", "nome": "raio", "estilo": "vidro", "tamanho": 220, "cor": "#ffb03a" }
482
+ ```
483
+
484
+ `app` é o azulejo de aplicativo: gradiente da cor, brilho na metade de cima,
485
+ sombra colorida embaixo, o traço branco. `vidro` é o mesmo azulejo em liquid
486
+ glass. `fundo` pinta o azulejo (sem ele, a cor do tema). Um ícone de traço
487
+ sozinho é sinal; no azulejo vira **coisa** — é o que se usa para "o app", "o
488
+ produto", "o recurso" numa cena de lançamento. Combina com `som: "pop"` na
489
+ entrada e com `chaves` de `giroY` para ele virar ao pousar.
431
490
 
432
491
  ---
433
492
 
@@ -647,11 +706,33 @@ Para controlar:
647
706
 
648
707
  ```json
649
708
  "sfx": false // no documento: desliga todos os efeitos
650
- { "transicao": "deslizar", "sfx": "impacto" } // na cena: força um efeito
709
+ { "transicao": "deslizar", "som": "impacto" } // na cena: força um efeito
710
+ ```
711
+
712
+ Valores: `"whoosh"` · `"impacto"` · `"click"` · `"pop"` · `"ding"` ·
713
+ `"subida"` · `"tique"` · `"brilho"` · `"nenhum"`. (`sfx` na cena ainda vale;
714
+ `som` é o nome novo, o mesmo dos elementos.)
715
+
716
+ ### Som por elemento — o que faz motion soar motion
717
+
718
+ `"som"` num **elemento** toca no instante em que ele entra (o mesmo atraso da
719
+ animação de entrada). É a diferença entre um slide animado e uma peça de
720
+ motion: o número faz **ding** ao aparecer, o ícone faz **pop** ao pousar, a
721
+ barra sobe com a **subida** (o riser) e o texto final chega com **brilho**.
722
+
723
+ ```json
724
+ { "tipo": "numeros", "itens": ["+38% | em 12 meses"], "contar": true, "som": "ding" }
725
+ { "tipo": "icone", "nome": "foguete", "estilo": "app", "som": "pop" }
726
+ { "tipo": "texto", "texto": "Agora.", "som": { "arquivo": "/midia/little-whoosh-3.mp3", "volume": 0.8 } }
727
+ { "tipo": "texto", "texto": "3, 2, 1", "som": { "nome": "tique", "em": 1.5 } }
651
728
  ```
652
729
 
653
- Valores por cena: `"whoosh"` · `"impacto"` · `"click"` · `"nenhum"`.
654
- O padrão (sem declarar nada) já é o certo para a maioria dos vídeos.
730
+ `som` aceita o nome, ou `{ "arquivo": "/midia/…", "volume": 0–1 }` para um
731
+ efeito baixado com `buscar_audio` (tipo `"efeito"`) ou enviado pelo usuário;
732
+ `"em"` (segundos na cena) força o instante. O arquivo entra no mesmo grafo da
733
+ trilha, então vai junto para o MP4. Regra: **um som por ideia, não por
734
+ elemento** — três ou quatro por vídeo; tudo apitando é ruído, e a narração
735
+ tem de continuar por cima.
655
736
 
656
737
  ---
657
738
 
@@ -684,8 +765,32 @@ Campos de uma chave, todos opcionais: `x` e `y` (**deslocamento** em pixels a
684
765
  partir de onde o elemento está — não é posição absoluta), `escala` (1 = tamanho
685
766
  normal), `giro` (graus), `opacidade` (0 a 1) e `desfoque` (pixels).
686
767
 
687
- No elemento: `"repetir": true` roda sem parar, e `"suavizar": false` dá
688
- movimento constante em vez de acelerar e frear.
768
+ **Em 3D:** `giroY` e `giroX` (graus — o elemento vira em profundidade, como um
769
+ cartão girando na mão), `prof` (pixels — aproxima ou afasta da câmera) e
770
+ `escalaX`/`escalaY` (achatar num eixo só: a gota que pousa e se espalha).
771
+
772
+ ```json
773
+ { "tipo": "icone", "nome": "foguete", "estilo": "app", "tamanho": 220,
774
+ "chaves": [
775
+ { "t": 0, "giroY": -90, "prof": -400, "opacidade": 0 },
776
+ { "t": 0.35, "giroY": 0, "prof": 0, "opacidade": 1 },
777
+ { "t": 1, "giroY": 12 }
778
+ ], "suavizar": "elastico", "som": "pop" }
779
+ ```
780
+
781
+ **A curva** (`suavizar`, no elemento): `"suave"` (padrão) · `"linear"` ·
782
+ `"entrada"` (acelera) · `"saida"` (freia) · `"elastico"` (passa do ponto e
783
+ volta — o pouso de app) · `"salto"` (recua, dispara e assenta) · `"degrau"`
784
+ (quadro a quadro, stop-motion). Aceita também um `cubic-bezier(…)` cru.
785
+ `"repetir": true` roda sem parar; com `"vaiEVolta": true` ele vai e volta em
786
+ vez de recomeçar — é a levitação de um objeto.
787
+
788
+ Um exemplo de cada coisa que motion pede — **entrar de lado, respirar, sair**:
789
+
790
+ ```json
791
+ "chaves": [ { "t": 0, "x": -300, "opacidade": 0 }, { "t": 0.2, "x": 0, "opacidade": 1 },
792
+ { "t": 0.85, "x": 0, "opacidade": 1 }, { "t": 1, "x": 120, "opacidade": 0 } ]
793
+ ```
689
794
 
690
795
  Duas chaves já bastam. Quem tem `chaves` ignora `anim` — as duas disputariam o
691
796
  mesmo movimento, e a chave é mais específica. Use em **um ou dois** elementos
@@ -61,6 +61,27 @@ você controla cada `x`, `y`, `l`, `a` em pixels da tela.
61
61
  `globo` `chave` `presente` `sino` `mapa` `dinheiro` `crescimento` `grafico` `balao`
62
62
  `pasta` `livro` `play2` `check` `imagem` `codigo` `tabela` `lista`
63
63
 
64
+ ## Vidro e ícone-objeto
65
+
66
+ `"material": "vidro"` em qualquer elemento é o liquid glass: painel
67
+ translúcido que **desfoca o que está atrás**, com bisel de luz em cima e
68
+ reflexo diagonal. Precisa de algo atrás (gradiente, forma colorida, foto) para
69
+ aparecer. Na forma, `preenchimento` vira a tinta do vidro.
70
+
71
+ ```json
72
+ { "tipo": "forma", "forma": "retangulo", "material": "vidro", "raio": 32,
73
+ "x": 86, "y": 300, "l": 908, "a": 380, "preenchimento": "#ff5a36" }
74
+ ```
75
+
76
+ `"estilo": "app"` no ícone faz o azulejo de aplicativo (gradiente da cor,
77
+ brilho, sombra, traço branco); `"estilo": "vidro"` faz o mesmo azulejo em
78
+ vidro. `fundo` pinta o azulejo. É o ícone de produto para capa e post de
79
+ lançamento:
80
+
81
+ ```json
82
+ { "tipo": "icone", "nome": "foguete", "estilo": "app", "x": 86, "y": 120, "tamanho": 200, "fundo": "#8b5cf6" }
83
+ ```
84
+
64
85
  ## Várias artes no mesmo arquivo
65
86
 
66
87
  Cada item de `cenas` é uma prancha independente. Um carrossel de Instagram é um