primocode 9.10.0 → 10.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +30 -6
  2. package/bin/primocode.js +169 -342
  3. package/lib/act.js +360 -39
  4. package/lib/browser.js +105 -4
  5. package/lib/casar.js +108 -0
  6. package/lib/catalogo.js +6 -2
  7. package/lib/claude-engine.js +38 -17
  8. package/lib/codex-engine.js +27 -9
  9. package/lib/config.js +10 -3
  10. package/lib/cursor.js +10 -1
  11. package/lib/entrada.js +77 -11
  12. package/lib/estudio/cinema.js +27 -9
  13. package/lib/estudio/diretor.js +21 -1
  14. package/lib/estudio/ende.js +16 -5
  15. package/lib/estudio/estilo.js +66 -0
  16. package/lib/estudio/gerativo.js +1453 -0
  17. package/lib/estudio/glifos.js +215 -0
  18. package/lib/estudio/hibrido.js +165 -0
  19. package/lib/estudio/index.js +284 -53
  20. package/lib/estudio/kit/base.jsx +285 -0
  21. package/lib/estudio/kit/gerativo.jsx +980 -0
  22. package/lib/estudio/kit/index.jsx +13 -0
  23. package/lib/estudio/kit/marca.jsx +741 -0
  24. package/lib/estudio/kit/tres.jsx +308 -0
  25. package/lib/estudio/kit/vox.jsx +357 -0
  26. package/lib/estudio/kit.js +78 -0
  27. package/lib/estudio/lancamento.js +441 -0
  28. package/lib/estudio/motion.js +2 -5
  29. package/lib/estudio/plano.js +28 -3
  30. package/lib/estudio/referencias.js +105 -0
  31. package/lib/estudio/remotion.js +139 -51
  32. package/lib/estudio/ritmo.js +77 -0
  33. package/lib/estudio/sons-leves.js +38 -1
  34. package/lib/estudio/variedade.js +105 -0
  35. package/lib/estudio/vox.js +414 -0
  36. package/lib/fala.js +1 -94
  37. package/lib/mcp.js +156 -0
  38. package/lib/personas.js +67 -0
  39. package/lib/pipeline.js +55 -56
  40. package/lib/repeticao.js +3 -1
  41. package/lib/skills.js +44 -2
  42. package/lib/snapshot.js +111 -0
  43. package/lib/tools.js +100 -22
  44. package/lib/ui.js +50 -8
  45. package/lib/video/analise.js +89 -0
  46. package/lib/video/auto.js +329 -0
  47. package/lib/video/camadas.js +207 -0
  48. package/lib/video/cli.js +74 -0
  49. package/lib/video/delta.js +90 -0
  50. package/lib/video/edits.js +379 -0
  51. package/lib/video/filters.js +280 -0
  52. package/lib/video/fx3d/Camadas.jsx +64 -0
  53. package/lib/video/fx3d/Objetos.jsx +128 -0
  54. package/lib/video/fx3d/Root.jsx +18 -0
  55. package/lib/video/fx3d/modelos.jsx +304 -0
  56. package/lib/video/fx3d.js +108 -0
  57. package/lib/video/gerados.js +119 -0
  58. package/lib/video/objetos.js +111 -0
  59. package/lib/video/ops-fx.js +453 -0
  60. package/lib/video/ops.js +347 -0
  61. package/lib/video/paralelo.js +50 -0
  62. package/lib/video/pedido.js +191 -0
  63. package/lib/video/projeto.js +133 -0
  64. package/lib/video/rastrear.js +95 -0
  65. package/lib/video/rastro.js +276 -0
  66. package/lib/video/render.js +274 -0
  67. package/lib/video/texto.js +144 -0
  68. package/lib/video/timeline.js +318 -0
  69. package/lib/video/transcript.js +124 -0
  70. package/lib/voz.js +75 -223
  71. package/package.json +2 -3
  72. package/skills/motion-de-primeira/SKILL.md +5 -5
  73. package/studio/narrar.py +10 -7
  74. package/studio/rastrear.py +399 -0
  75. package/studio/texto.py +208 -0
  76. package/voz/primo_voz/__main__.py +96 -236
  77. package/voz/primo_voz/transcrever.py +31 -1
  78. package/app/ansi.js +0 -55
  79. package/app/app.css +0 -500
  80. package/app/app.js +0 -1017
  81. package/app/icone.svg +0 -6
  82. package/app/index.html +0 -220
  83. package/app/primo-marca.js +0 -192
  84. package/lib/app.js +0 -615
  85. package/lib/icone.js +0 -263
  86. package/lib/instalar-app.js +0 -231
  87. package/lib/painel.js +0 -291
  88. package/voz/primo_voz/desperta.py +0 -195
  89. package/voz/primo_voz/orbe.py +0 -406
@@ -0,0 +1,741 @@
1
+ /* kit/marca.jsx — as peças de marca e de lançamento.
2
+ *
3
+ * O repertório dos vídeos de referência (brand reveal, reel de motion
4
+ * designer, lançamento de SaaS), cada um virado peça:
5
+ *
6
+ * orbe o brilho no escuro, anéis e poeira; a marca nasce dele
7
+ * vetor as letras em contorno, com âncoras, alças de bezier e o cursor
8
+ * manchete a frase cinética: carimbo, desfoque, digitação ou subida; sai em pontos
9
+ * cromo a marca cromada (ou bordada) com a luz de estúdio passando
10
+ * cores os cartões de cor flutuando em 3D, com borrão de movimento
11
+ * padrao a marca ladrilhada (ou o truchet) com a onda passando
12
+ * particulas a explosão de partículas
13
+ * estrela o asterisco que explode no anel com texto em volta
14
+ * cartoes cartões de interface: voo em 3D, colunas de calendário, mural
15
+ * painel o cartão do produto: o marca-texto, o selo, as barras enchendo
16
+ * esfera a esfera de pontos girando em 3D
17
+ * grade o chão em perspectiva
18
+ * site a janela do site com a manchete entrando
19
+ *
20
+ * Coordenadas da cena em px; tempo do elemento em segundos. */
21
+ import React from 'react';
22
+ import { Img, staticFile } from 'remotion';
23
+ import {
24
+ mola, entre, lerp, acaso, Traco, suave, familia, MONO, CURVAS_KIT, mistura, alfa, sobre, Marca,
25
+ } from './base';
26
+
27
+ const ED = CURVAS_KIT.editorial;
28
+
29
+ // ── as palavras que entram (manchete e site dividem) ────────────────────
30
+ function entradaPalavra(modo, t, t0, fps, i) {
31
+ if (t < t0) return { opacity: 0 };
32
+ const k = ED(entre(t, t0, t0 + 10 / fps));
33
+ if (modo === 'desfoque') {
34
+ return { opacity: Math.min(1, k * 1.6), filter: k < 1 ? 'blur(' + ((1 - k) * 18).toFixed(1) + 'px)' : undefined,
35
+ transform: 'translateY(' + ((1 - k) * 0.22).toFixed(3) + 'em)' };
36
+ }
37
+ if (modo === 'subir') return { transform: 'translateY(' + ((1 - k) * 105).toFixed(1) + '%)' };
38
+ // carimbo: rotação e escala que assentam; nunca fade.
39
+ const m = mola(t, t0, fps);
40
+ const r0 = (acaso(i + 5) - 0.5) * 6;
41
+ return { opacity: 1, transform: 'rotate(' + (r0 * (1 - m)).toFixed(2) + 'deg) scale(' + (1 + 0.22 * (1 - m)).toFixed(3) + ')' };
42
+ }
43
+
44
+ // ── manchete cinética ────────────────────────────────────────────────────
45
+ export function Manchete({ el, t, fps }) {
46
+ const texto = String(el.texto || '');
47
+ const modo = el.modo || 'desfoque';
48
+ const t0 = el.t || 0;
49
+ const passo = (el.passo || 4) / fps;
50
+ const tam = el.tamanho || 140;
51
+ const cor = el.cor || '#111';
52
+ const acento = el.corAcento || cor;
53
+ const alvos = new Set(String(el.destaque || '').toLowerCase().split(/\s+/).filter(Boolean).map((w) => w.replace(/[^\p{L}\p{N}]/gu, '')));
54
+ const centro = el.alinhamento === 'center';
55
+ const caixa = { position: 'absolute', left: el.x, top: el.y, width: el.largura, transform: centro ? 'translateX(-50%)' : (el.ancora === 'meio' ? 'translateY(-50%)' : undefined),
56
+ textAlign: centro ? 'center' : 'left', fontFamily: familia(el.fonte), fontWeight: el.peso || 900, fontSize: tam, lineHeight: el.entrelinha || 0.98,
57
+ letterSpacing: el.espacamento ?? '-0.035em', color: cor, textTransform: el.caixaAlta ? 'uppercase' : undefined };
58
+
59
+ // O NÚMERO que conta: sobe de 0 até o valor na curva editorial.
60
+ if (el.contar) {
61
+ const k = ED(entre(t, t0, t0 + (el.contar.dur || 0.9)));
62
+ const v = (Number(el.contar.ate) * k).toLocaleString('pt-BR', { maximumFractionDigits: el.contar.casas || 0, minimumFractionDigits: el.contar.casas || 0 });
63
+ return <div style={{ ...caixa, fontVariantNumeric: 'tabular-nums', ...entradaPalavra('carimbo', t, t0, fps, 1) }}>{(el.contar.antes || '') + v + (el.contar.depois || '')}</div>;
64
+ }
65
+
66
+ // A DIGITAÇÃO: letra a letra, com o cursor piscando (o wordmark que se escreve).
67
+ if (modo === 'digitar') {
68
+ const n = Math.max(0, Math.min(texto.length, Math.floor((t - t0) * fps / 1.6) + 1));
69
+ const pisca = Math.floor(t * fps / 9) % 2 === 0 && t < t0 + texto.length * 1.6 / fps + 0.8;
70
+ return (
71
+ <div style={{ ...caixa, whiteSpace: 'nowrap', opacity: t >= t0 ? 1 : 0 }}>
72
+ {texto.slice(0, n)}<span style={{ opacity: pisca ? 1 : 0, fontWeight: 300, marginLeft: '0.02em' }}>|</span>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ const ws = texto.split(/\s+/).filter(Boolean);
78
+ const saida = el.saida && t >= el.saida.t ? el.saida : null;
79
+ let letra = 0;
80
+ return (
81
+ <div style={caixa}>
82
+ {ws.map((w, i) => {
83
+ const tw = el.tempos && el.tempos[i] != null ? el.tempos[i] : t0 + i * passo;
84
+ const ehAlvo = alvos.has(w.toLowerCase().replace(/[^\p{L}\p{N}]/gu, ''));
85
+ const quebra = el.quebras && el.quebras.includes(i);
86
+ const letras = w.split('');
87
+ const conteudo = saida && saida.modo === 'pontos'
88
+ ? letras.map((c, j) => {
89
+ // As letras DESMANCHAM em pontos, uma depois da outra, e os pontos somem.
90
+ const n = letra++;
91
+ const tl = saida.t + n * 1.2 / fps;
92
+ const k = ED(entre(t, tl, tl + 9 / fps));
93
+ const some = entre(t, tl + 14 / fps, tl + 22 / fps);
94
+ const r = (acaso(n + 9) - 0.5) * 70;
95
+ return (
96
+ <span key={j} style={{ position: 'relative', display: 'inline-block' }}>
97
+ <span style={{ display: 'inline-block', transform: 'scale(' + (1 - k * 0.92).toFixed(3) + ')', opacity: 1 - k }}>{c}</span>
98
+ <span style={{ position: 'absolute', left: '50%', top: '55%', width: '0.26em', height: '0.2em', borderRadius: '50%', background: ehAlvo ? acento : cor,
99
+ transform: 'translate(-50%,-50%) rotate(' + r + 'deg) scale(' + (k * (1 - some)).toFixed(3) + ')' }} />
100
+ </span>
101
+ );
102
+ })
103
+ : w;
104
+ const estilo = saida && saida.modo === 'desfoque'
105
+ ? { filter: 'blur(' + (entre(t, saida.t, saida.t + 8 / fps) * 20).toFixed(1) + 'px)', opacity: 1 - entre(t, saida.t, saida.t + 8 / fps) }
106
+ : entradaPalavra(modo, t, tw, fps, i);
107
+ return (
108
+ <React.Fragment key={i}>
109
+ {quebra ? <br /> : null}
110
+ <span style={{ display: 'inline-block', overflow: modo === 'subir' ? 'hidden' : undefined, verticalAlign: 'top', paddingBottom: modo === 'subir' ? '0.08em' : undefined }}>
111
+ <span style={{ display: 'inline-block', color: ehAlvo ? acento : undefined, transformOrigin: '50% 80%', ...estilo }}>{conteudo}</span>
112
+ </span>
113
+ {i < ws.length - 1 ? ' ' : null}
114
+ </React.Fragment>
115
+ );
116
+ })}
117
+ </div>
118
+ );
119
+ }
120
+
121
+ // ── o orbe ───────────────────────────────────────────────────────────────
122
+ export function Orbe({ el, t, fps }) {
123
+ const R = el.tamanho || 260;
124
+ const cor = el.cor || '#ff5a1f';
125
+ const t0 = el.t || 0;
126
+ const tM = el.tMarca;
127
+ const nasce = mola(t, t0, fps);
128
+ const vira = tM != null ? ED(entre(t, tM, tM + 12 / fps)) : 0;
129
+ const respira = 1 + 0.04 * Math.sin(t * 2.2);
130
+ const pts = [];
131
+ for (let i = 0; i < (el.particulas ?? 46); i++) {
132
+ const a = acaso(i + 1) * Math.PI * 2;
133
+ const d0 = R * (0.9 + acaso(i + 50) * 3.2);
134
+ const d = d0 + t * (8 + acaso(i + 90) * 22);
135
+ const s = 1.5 + acaso(i + 130) * 2.6;
136
+ const pisca = 0.35 + 0.65 * Math.abs(Math.sin(t * (1 + acaso(i + 7) * 2) + i));
137
+ pts.push(<circle key={i} cx={el.x + Math.cos(a) * d} cy={el.y + Math.sin(a) * d * 0.8} r={s} fill={i % 5 === 0 ? cor : '#ffffff'} opacity={pisca * 0.55 * nasce} />);
138
+ }
139
+ return (
140
+ <>
141
+ <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', overflow: 'visible' }}>
142
+ {el.aneis !== false && [1.35, 2.1, 3.0].map((k, i) => (
143
+ <circle key={i} cx={el.x} cy={el.y} r={R * k * (0.9 + 0.1 * nasce) + t * 6 * (i + 1)} fill="none" stroke={alfa(cor, 0.22 - i * 0.05)} strokeWidth={1.4} />
144
+ ))}
145
+ {pts}
146
+ </svg>
147
+ <div style={{ position: 'absolute', left: el.x - R * 2, top: el.y - R * 2, width: R * 4, height: R * 4, borderRadius: '50%',
148
+ background: 'radial-gradient(circle, ' + alfa(cor, 0.55) + ' 0%, ' + alfa(cor, 0.16) + ' 30%, transparent 62%)',
149
+ transform: 'scale(' + (nasce * respira * (1 + vira * 0.25)) + ')', opacity: 1 - vira * 0.35 }} />
150
+ <div style={{ position: 'absolute', left: el.x - R * 0.2, top: el.y - R * 0.2, width: R * 0.4, height: R * 0.4, borderRadius: '50%',
151
+ background: 'radial-gradient(circle at 40% 35%, ' + mistura(cor, '#ffffff', 0.5) + ', ' + cor + ' 60%)', boxShadow: '0 0 ' + R * 0.3 + 'px ' + alfa(cor, 0.8),
152
+ transform: 'scale(' + (nasce * (1 - vira)) + ')' }} />
153
+ {tM != null && t >= tM ? (
154
+ <div style={{ position: 'absolute', left: el.x, top: el.y, transform: 'translate(-50%,-50%) scale(' + (0.55 + 0.45 * mola(t, tM, fps)) + ')',
155
+ filter: 'drop-shadow(0 0 ' + R * 0.18 + 'px ' + alfa(el.corMarca || cor, 0.85) + ')' }}>
156
+ <Marca logo={el.logo} tamanho={R * 0.62} cor={el.corMarca || cor} tinta={el.logo ? undefined : 'linear-gradient(160deg, #ffffff, ' + mistura(el.corMarca || cor, '#ffffff', 0.4) + ')'} />
157
+ </div>
158
+ ) : null}
159
+ </>
160
+ );
161
+ }
162
+
163
+ // ── as letras em vetor ───────────────────────────────────────────────────
164
+ /* "glifos" vem do PrimoCode (glifos.js leu a fonte TrueType): o caminho de
165
+ cada letra e os pontos de verdade — os que ficam na curva viram âncora
166
+ (quadrado), os de controle viram alça (bolinha com a haste). Sem glifos
167
+ (sem rede na hora de montar), o contorno sai do próprio texto, sem alças. */
168
+ export function Vetor({ el, t, fps }) {
169
+ const g = el.glifos;
170
+ const t0 = el.t || 0;
171
+ const durTraco = el.durTraco || 1.1;
172
+ const pT = CURVAS_KIT.arrasto(entre(t, t0, t0 + durTraco));
173
+ const pFill = ED(entre(t, t0 + durTraco * 0.75, t0 + durTraco + 0.5));
174
+ const H = el.altura || 600;
175
+ const cor = el.cor || '#ffffff';
176
+ const azul = el.corPonto || '#2f8cff';
177
+ const pan = el.pan ? CURVAS_KIT.camera(entre(t, t0, t0 + (el.pan.dur || 3))) : 0;
178
+ const dx = el.pan ? lerp(el.pan.de || 0, el.pan.ate || 0, pan) : 0;
179
+ if (!g) {
180
+ return (
181
+ <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', overflow: 'visible' }}>
182
+ <text x={el.x + dx} y={el.y} fontFamily={familia(el.fonte)} fontWeight={el.peso || 800} fontSize={H} textAnchor="middle" dominantBaseline="central"
183
+ fill={alfa('#2a2a30', pFill)} stroke={cor} strokeWidth={Math.max(2, H * 0.006)} strokeDasharray={H * 30} strokeDashoffset={H * 30 * (1 - pT)}>{el.texto}</text>
184
+ </svg>
185
+ );
186
+ }
187
+ const [bx, by, bw, bh] = g.caixa;
188
+ const esc = H / bh;
189
+ const x0 = el.x - (bw * esc) / 2 + dx, y0 = el.y - (bh * esc) / 2;
190
+ const P = (p) => [x0 + (p[0] - bx) * esc, y0 + (p[1] - by) * esc];
191
+ const pontos = g.pontos || [];
192
+ const N = pontos.length;
193
+ const q = Math.max(6, H * 0.022);
194
+ const itens = [];
195
+ pontos.forEach((p, i) => {
196
+ const tp = t0 + (i / Math.max(1, N)) * durTraco * 0.95;
197
+ if (t < tp) return;
198
+ const m = mola(t, tp, fps);
199
+ const [x, y] = P(p);
200
+ if (p[2]) {
201
+ itens.push(<rect key={'a' + i} x={x - q / 2} y={y - q / 2} width={q} height={q} fill={azul} stroke="#ffffff" strokeWidth={1.2}
202
+ transform={'rotate(0) translate(0 0)'} opacity={m} style={{ transformBox: 'fill-box', transformOrigin: 'center', transform: 'scale(' + (0.4 + 0.6 * m) + ')' }} />);
203
+ } else {
204
+ // A alça: a haste até as âncoras vizinhas, e a bolinha na ponta.
205
+ const viz = [pontos[(i - 1 + N) % N], pontos[(i + 1) % N]].filter((v) => v && v[3] === p[3]);
206
+ for (const [k, v] of viz.entries()) {
207
+ const [vx, vy] = P(v);
208
+ itens.push(<line key={'h' + i + k} x1={x} y1={y} x2={vx} y2={vy} stroke={azul} strokeWidth={1.3} opacity={0.9 * m} />);
209
+ }
210
+ itens.push(<circle key={'c' + i} cx={x} cy={y} r={q * 0.36 * (0.4 + 0.6 * m)} fill="#ffffff" stroke={azul} strokeWidth={1.4} />);
211
+ }
212
+ });
213
+ // O cursor passeia pelas âncoras, como quem ajusta a curva.
214
+ const ancoras = pontos.filter((p) => p[2]);
215
+ let cursor = null;
216
+ if (el.cursor !== false && ancoras.length && t >= t0 + 0.3) {
217
+ const seg = 0.55;
218
+ const k = (t - t0 - 0.3) / seg;
219
+ const i = Math.floor(k);
220
+ const a = P(ancoras[(i * 7) % ancoras.length]), b = P(ancoras[((i + 1) * 7) % ancoras.length]);
221
+ const e = ED(Math.min(1, (k - i) * 1.4));
222
+ const cx = lerp(a[0], b[0], e) + q, cy = lerp(a[1], b[1], e) + q;
223
+ const s = Math.max(22, H * 0.06);
224
+ cursor = <path d={'M ' + cx + ' ' + cy + ' l 0 ' + s + ' l ' + s * 0.28 + ' ' + -s * 0.26 + ' l ' + s * 0.42 + ' ' + -s * 0.02 + ' Z'}
225
+ fill="#d9dde6" stroke="#1b1d22" strokeWidth={1.5} strokeLinejoin="round" />;
226
+ }
227
+ const dGlifos = 'translate(' + x0 + ' ' + y0 + ') scale(' + esc + ') translate(' + -bx + ' ' + -by + ')';
228
+ return (
229
+ <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', overflow: 'visible' }}>
230
+ <defs>
231
+ <linearGradient id="vetor-preenche" x1="0" y1="0" x2="0" y2="1">
232
+ <stop offset="0" stopColor={el.corTopo || '#3a3b42'} />
233
+ <stop offset="1" stopColor={el.corBase || '#101114'} />
234
+ </linearGradient>
235
+ </defs>
236
+ <g transform={dGlifos}>
237
+ <path d={g.d} fill="url(#vetor-preenche)" fillOpacity={pFill} fillRule="nonzero" />
238
+ <path d={g.d} fill="none" stroke={cor} strokeWidth={Math.max(1.6, H * 0.004) / esc} pathLength={1} strokeDasharray="1 1" strokeDashoffset={1 - pT} />
239
+ </g>
240
+ {itens}
241
+ {cursor}
242
+ </svg>
243
+ );
244
+ }
245
+
246
+ // ── a marca cromada ──────────────────────────────────────────────────────
247
+ export function Cromo({ el, t, fps }) {
248
+ const s = el.tamanho || 320;
249
+ const t0 = el.t || 0;
250
+ const m = mola(t, t0, fps, { damping: 30, stiffness: 120, mass: 1 });
251
+ const giro = (el.giro ?? 16) * Math.sin((t - t0) * 0.9);
252
+ const ang = 115 + 70 * Math.sin((t - t0) * 0.7);
253
+ const passa = ((t - t0) * 0.45) % 1.6 - 0.3; // a luz de estúdio que atravessa
254
+ const modo = el.modo || 'cromo';
255
+ const tom = el.tom || '#9aa0aa';
256
+ const tinta = modo === 'bordado'
257
+ ? 'repeating-linear-gradient(' + (45 + giro * 0.2) + 'deg, ' + mistura(tom, '#ffffff', 0.25) + ' 0px, ' + tom + ' 2px, ' + mistura(tom, '#000000', 0.35) + ' 4px, ' + tom + ' 6px)'
258
+ : modo === 'solido'
259
+ ? tom
260
+ : 'linear-gradient(' + ang + 'deg, #1c1d21 0%, #f4f5f7 14%, #7d8088 27%, #ffffff 40%, #2e3036 52%, #c9ccd3 66%, #5f626a 80%, #eef0f3 92%, #3a3c42 100%)';
261
+ const brilho = 'linear-gradient(105deg, transparent ' + (passa * 100 - 12) + '%, rgba(255,255,255,0.95) ' + passa * 100 + '%, transparent ' + (passa * 100 + 12) + '%)';
262
+ const sombra = modo === 'bordado'
263
+ ? 'drop-shadow(0 2px 0 rgba(0,0,0,0.5)) drop-shadow(0 -1px 0 rgba(255,255,255,0.18)) drop-shadow(0 16px 26px rgba(0,0,0,0.5))'
264
+ : 'drop-shadow(0 1px 0 rgba(255,255,255,0.55)) drop-shadow(0 -1px 1px rgba(0,0,0,0.6)) drop-shadow(0 22px 34px rgba(0,0,0,0.45))';
265
+ return (
266
+ <div style={{ position: 'absolute', left: el.x, top: el.y, width: s, height: s, transform: 'translate(-50%,-50%)', perspective: 1200, opacity: t >= t0 ? 1 : 0 }}>
267
+ <div style={{ position: 'absolute', inset: 0, transformStyle: 'preserve-3d',
268
+ transform: 'rotateY(' + (lerp(-50, 0, m) + giro) + 'deg) rotateX(' + (6 * Math.sin((t - t0) * 0.6)) + 'deg) scale(' + (0.72 + 0.28 * m) + ')' }}>
269
+ <div style={{ position: 'absolute', inset: 0, filter: sombra }}><Marca logo={el.logo} tamanho={s} tinta={tinta} /></div>
270
+ {modo !== 'bordado' ? <div style={{ position: 'absolute', inset: 0, mixBlendMode: 'screen' }}><Marca logo={el.logo} tamanho={s} tinta={brilho} /></div> : null}
271
+ </div>
272
+ {el.reflexo !== false && modo !== 'bordado' ? (
273
+ <div style={{ position: 'absolute', left: 0, top: s * 1.04, width: s, height: s, transform: 'scaleY(-1) rotateY(' + (lerp(-50, 0, m) + giro) + 'deg)',
274
+ opacity: 0.14 * m, WebkitMaskImage: 'linear-gradient(to top, rgba(0,0,0,0.9), transparent 55%)', maskImage: 'linear-gradient(to top, rgba(0,0,0,0.9), transparent 55%)' }}>
275
+ <Marca logo={el.logo} tamanho={s} tinta={tinta} />
276
+ </div>
277
+ ) : null}
278
+ </div>
279
+ );
280
+ }
281
+
282
+ // ── os cartões de cor ────────────────────────────────────────────────────
283
+ export function Cores({ el, t, fps }) {
284
+ const cores = el.cores || [];
285
+ const n = cores.length;
286
+ const w = el.largura || 300, h = w * 0.82;
287
+ const Rx = el.raioX || 560, Ry = el.raioY || 300;
288
+ const t0 = el.t || 0;
289
+ return (
290
+ <div style={{ position: 'absolute', inset: 0, perspective: 1600 }}>
291
+ {cores.map((c, i) => {
292
+ const a = -Math.PI * 0.8 + (i / Math.max(1, n - 1)) * Math.PI * 1.6 * (n > 1 ? 1 : 0) + (acaso(i + 2) - 0.5) * 0.35;
293
+ const alvoX = el.x + Math.cos(a) * Rx * (0.8 + acaso(i + 4) * 0.3);
294
+ const alvoY = el.y + Math.sin(a) * Ry * (0.8 + acaso(i + 6) * 0.35);
295
+ const de = t0 + i * 5 / fps;
296
+ const k = mola(t, de, fps, { damping: 22, stiffness: 110, mass: 0.8 });
297
+ const kAntes = mola(t - 1 / fps, de, fps, { damping: 22, stiffness: 110, mass: 0.8 });
298
+ // O borrão de movimento é a velocidade do quadro: rápido borra, parado fica nítido.
299
+ const vel = Math.abs(k - kAntes);
300
+ const fora = 1.9;
301
+ const x = lerp(el.x + Math.cos(a) * Rx * fora * 1.6, alvoX, k);
302
+ const y = lerp(el.y + Math.sin(a) * Ry * fora * 1.6, alvoY, k) + Math.sin(t * 1.3 + i * 1.7) * 8;
303
+ const z = -120 + acaso(i + 8) * 260;
304
+ const ry = (Math.cos(a) * -22) + Math.sin(t * 0.8 + i) * 4;
305
+ const rx = (Math.sin(a) * 14);
306
+ const rz = (acaso(i + 10) - 0.5) * 14;
307
+ const txt = sobre(c.hex || '#888');
308
+ return (
309
+ <div key={i} style={{ position: 'absolute', left: x - w / 2, top: y - h / 2, width: w, height: h, borderRadius: w * 0.07, background: c.hex,
310
+ transform: 'translateZ(' + z + 'px) rotateX(' + rx + 'deg) rotateY(' + ry + 'deg) rotate(' + rz + 'deg) scaleX(' + (1 + vel * 2.2) + ')',
311
+ filter: vel > 0.004 ? 'blur(' + Math.min(22, vel * 520).toFixed(1) + 'px)' : undefined, opacity: t >= de ? 1 : 0,
312
+ boxShadow: '0 30px 60px -20px rgba(0,0,0,0.35), 0 2px 4px rgba(0,0,0,0.12)', border: luzBorda(c.hex) }}>
313
+ <div style={{ position: 'absolute', left: w * 0.08, top: w * 0.07, fontFamily: familia(el.fonte), fontWeight: 600, fontSize: w * 0.085, color: txt }}>{c.nome}</div>
314
+ <div style={{ position: 'absolute', left: w * 0.08, bottom: w * 0.07, fontFamily: MONO(el.fonteMono), fontSize: w * 0.052, color: alfa(txt, 0.7), letterSpacing: '0.04em' }}>{String(c.hex || '').toUpperCase()}</div>
315
+ </div>
316
+ );
317
+ })}
318
+ </div>
319
+ );
320
+ }
321
+ const luzBorda = (hex) => (sobre(hex) === '#111114' ? '1px solid rgba(0,0,0,0.06)' : '1px solid rgba(255,255,255,0.08)');
322
+
323
+ // ── o padrão ─────────────────────────────────────────────────────────────
324
+ export function Padrao({ el, t, fps }) {
325
+ const W = el.largura || 1920, H = el.altura || 1080;
326
+ const c = el.celula || 180;
327
+ const cols = Math.ceil(W / c) + 2, lins = Math.ceil(H / c) + 2;
328
+ const t0 = el.t || 0;
329
+ const cor = el.cor || '#ffffff';
330
+ const onda = el.corOnda || cor;
331
+ const cx = cols / 2, cy = lins / 2;
332
+ const deriva = (t - t0) * (el.deriva ?? 24);
333
+ const cel = [];
334
+ for (let j = 0; j < lins; j++) {
335
+ for (let i = 0; i < cols; i++) {
336
+ const dist = Math.hypot(i - cx, j - cy);
337
+ const de = t0 + dist * 1.3 / fps;
338
+ if (t < de) continue;
339
+ const m = mola(t, de, fps);
340
+ const fase = (t - t0) * 3.2 - dist * 0.55;
341
+ const w = Math.sin(fase);
342
+ const x = (i - 1) * c + ((j % 2) * c) / 2 - (deriva % c), y = (j - 1) * c;
343
+ if (el.modo === 'truchet') {
344
+ const gira = acaso(i * 31 + j * 17) > 0.5 ? 90 : 0;
345
+ const aceso = w > 0.55;
346
+ const traco = aceso ? mistura(cor, onda, Math.min(1, (w - 0.55) * 3)) : cor;
347
+ cel.push(
348
+ <svg key={i + '-' + j} viewBox="0 0 100 100" width={c} height={c} style={{ position: 'absolute', left: i * c - c - (deriva % c), top: y,
349
+ transform: 'rotate(' + gira + 'deg) scale(' + m + ')' }}>
350
+ <path d="M 50 0 A 50 50 0 0 1 0 50 M 100 50 A 50 50 0 0 0 50 100" fill="none" stroke={traco} strokeWidth={el.espessura || 16} strokeLinecap="round" />
351
+ </svg>,
352
+ );
353
+ } else {
354
+ const s = c * (el.escala || 0.52) * m * (0.86 + 0.14 * w);
355
+ cel.push(
356
+ <div key={i + '-' + j} style={{ position: 'absolute', left: x + c / 2 - s / 2, top: y + c / 2 - s / 2, width: s, height: s,
357
+ transform: 'rotate(' + (w * 10) + 'deg)', opacity: 0.9 + 0.1 * w }}>
358
+ <Marca logo={el.logo} tamanho={s} tinta={w > 0.8 ? onda : cor} />
359
+ </div>,
360
+ );
361
+ }
362
+ }
363
+ }
364
+ return <div style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: W, height: H, overflow: 'hidden' }}>{cel}</div>;
365
+ }
366
+
367
+ // ── a explosão de partículas ─────────────────────────────────────────────
368
+ export function Particulas({ el, t, fps }) {
369
+ const t0 = el.t || 0;
370
+ if (t < t0) return null;
371
+ const n = el.n || 42;
372
+ const R = el.raio || 420;
373
+ const cores = el.cores || ['#ffffff'];
374
+ const dt = t - t0;
375
+ const k = ED(entre(t, t0, t0 + 1.0));
376
+ const pts = [];
377
+ for (let i = 0; i < n; i++) {
378
+ const a = acaso(i + 3) * Math.PI * 2;
379
+ const v = 0.35 + acaso(i + 40) * 0.65;
380
+ const d = R * v * k;
381
+ const x = el.x + Math.cos(a) * d;
382
+ const y = el.y + Math.sin(a) * d + dt * dt * 60 * v;
383
+ const s = (el.tamanho || 7) * (0.5 + acaso(i + 80)) * (1 - entre(t, t0 + 0.5, t0 + 1.4) * 0.8);
384
+ const op = 1 - entre(t, t0 + 0.7 + acaso(i) * 0.5, t0 + 1.5);
385
+ const cor = cores[i % cores.length];
386
+ pts.push(i % 4 === 3 && el.forma !== 'circulo'
387
+ ? <path key={i} d={'M ' + x + ' ' + (y - s) + ' L ' + (x + s) + ' ' + (y + s) + ' L ' + (x - s) + ' ' + (y + s) + ' Z'} fill={cor} opacity={op} transform={'rotate(' + a * 57 + ' ' + x + ' ' + y + ')'} />
388
+ : <circle key={i} cx={x} cy={y} r={s / 2 + 0.5} fill={cor} opacity={op} />);
389
+ }
390
+ return <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', overflow: 'visible' }}>{pts}</svg>;
391
+ }
392
+
393
+ // ── o asterisco e o anel ─────────────────────────────────────────────────
394
+ export function Estrela({ el, t, fps }) {
395
+ const R = el.tamanho || 200;
396
+ const t0 = el.t || 0;
397
+ const cor = el.cor || '#ff5a1f';
398
+ const n = el.raios || 14;
399
+ const tb = t0 + (el.espera ?? 0.45);
400
+ const gira = (t - t0) * 12;
401
+ const raios = [];
402
+ for (let i = 0; i < n; i++) {
403
+ const m = mola(t, tb + (i % 3) / fps, fps);
404
+ const a = (i / n) * 360 + gira;
405
+ const L = R * (0.72 + acaso(i + 1) * 0.35) * m;
406
+ raios.push(<line key={i} x1={0} y1={0} x2={0} y2={-L} stroke={cor} strokeWidth={R * 0.13} strokeLinecap="round" transform={'rotate(' + a + ')'} />);
407
+ }
408
+ const ponto = t < tb ? mola(t, t0, fps) : 1 - mola(t, tb, fps);
409
+ const pAnel = CURVAS_KIT.arrasto(entre(t, t0, t0 + 0.8));
410
+ const texto = String(el.texto || '');
411
+ return (
412
+ <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', overflow: 'visible' }}>
413
+ <defs><path id="estrela-anel" d={'M ' + (el.x - R * 1.6) + ' ' + el.y + ' a ' + R * 1.6 + ' ' + R * 1.6 + ' 0 1 1 ' + R * 3.2 + ' 0 a ' + R * 1.6 + ' ' + R * 1.6 + ' 0 1 1 ' + -R * 3.2 + ' 0'} /></defs>
414
+ <Traco d={'M ' + (el.x - R * 1.95) + ' ' + el.y + ' a ' + R * 1.95 + ' ' + R * 1.95 + ' 0 1 1 ' + R * 3.9 + ' 0 a ' + R * 1.95 + ' ' + R * 1.95 + ' 0 1 1 ' + -R * 3.9 + ' 0'}
415
+ p={pAnel} cor={alfa(el.corAnel || '#ffffff', 0.35)} espessura={1.4} />
416
+ {texto ? (
417
+ <g transform={'rotate(' + (-gira * 0.6) + ' ' + el.x + ' ' + el.y + ')'} opacity={entre(t, t0 + 0.3, t0 + 0.7)}>
418
+ <text fontFamily={MONO(el.fonteMono)} fontSize={R * 0.12} letterSpacing={R * 0.05} fill={alfa(el.corAnel || '#ffffff', 0.7)}>
419
+ <textPath href="#estrela-anel">{(texto.toUpperCase() + ' · ').repeat(4)}</textPath>
420
+ </text>
421
+ </g>
422
+ ) : null}
423
+ <g transform={'translate(' + el.x + ' ' + el.y + ')'}>
424
+ {raios}
425
+ {ponto > 0.01 ? <circle r={R * 0.16 * ponto} fill={cor} /> : null}
426
+ </g>
427
+ </svg>
428
+ );
429
+ }
430
+
431
+ // ── os cartões de interface ──────────────────────────────────────────────
432
+ function Cartao({ c, w, el, destaque }) {
433
+ const acento = el.corAcento || '#ff5a1f';
434
+ const fonte = familia(el.fonte);
435
+ return (
436
+ <div style={{ width: w, background: el.cor || '#ffffff', borderRadius: w * 0.045, padding: w * 0.055, boxSizing: 'border-box',
437
+ boxShadow: '0 1px 2px rgba(0,0,0,0.08), 0 24px 48px -18px rgba(0,0,0,0.35)', fontFamily: fonte, color: el.corTexto || '#16171b',
438
+ border: destaque ? '2px solid ' + alfa(acento, 0.8) : '1px solid rgba(0,0,0,0.05)' }}>
439
+ <div style={{ display: 'flex', alignItems: 'center', gap: w * 0.03, marginBottom: w * 0.035 }}>
440
+ <div style={{ width: w * 0.085, height: w * 0.085, borderRadius: '50%', background: c.cor || mistura(acento, '#ffffff', 0.55) }} />
441
+ <div style={{ fontWeight: 700, fontSize: w * 0.05 }}>{c.titulo || ''}</div>
442
+ {c.rotulo ? <div style={{ marginLeft: 'auto', fontFamily: MONO(el.fonteMono), fontSize: w * 0.034, color: '#8a8d96' }}>{c.rotulo}</div> : null}
443
+ </div>
444
+ {c.texto ? <div style={{ fontSize: w * (String(c.texto).length < 22 ? 0.085 : 0.055), lineHeight: 1.25, fontWeight: String(c.texto).length < 22 ? 700 : 500, letterSpacing: '-0.01em' }}>{c.texto}</div> : null}
445
+ {!c.texto ? [0.92, 0.7, 0.45].map((k, i) => <div key={i} style={{ height: w * 0.028, width: k * 100 + '%', borderRadius: 99, background: '#e8e9ed', marginTop: w * 0.028 }} />) : null}
446
+ </div>
447
+ );
448
+ }
449
+
450
+ export function Cartoes({ el, t, fps }) {
451
+ const lista = el.cartoes || [];
452
+ const modo = el.modo || 'voo';
453
+ const t0 = el.t || 0;
454
+ const W = el.largura || 1920, H = el.altura || 1080;
455
+ const X = el.x || 0, Y = el.y || 0;
456
+ const acento = el.corAcento || '#ff5a1f';
457
+
458
+ if (modo === 'colunas') {
459
+ const n = Math.max(1, lista.length);
460
+ const gap = W * 0.018;
461
+ const cw = (W - gap * (n - 1)) / n;
462
+ return (
463
+ <div style={{ position: 'absolute', left: X, top: Y, width: W, height: H, display: 'flex', gap }}>
464
+ {lista.map((c, i) => {
465
+ const de = t0 + i * 4 / fps;
466
+ const m = mola(t, de, fps);
467
+ const itens = c.itens || [];
468
+ /* A coluna da VEZ é a que está sendo falada ("t" dela): sobe um pouco e
469
+ ganha a borda do acento; as outras ficam como estão. É o que dá vida
470
+ a um calendário que fica muitos segundos na tela. */
471
+ let vez = -1;
472
+ lista.forEach((q, j) => { if (q.t != null && t >= q.t) vez = j; });
473
+ const ativa = vez === i ? mola(t, c.t || 0, fps) : (vez === i + 1 ? 1 - mola(t, lista[vez].t, fps) : 0); // a anterior desce, não some
474
+ return (
475
+ <div key={i} style={{ width: cw, height: H, background: el.cor || '#ffffff', borderRadius: cw * 0.06, padding: cw * 0.08, boxSizing: 'border-box',
476
+ boxShadow: '0 1px 2px rgba(0,0,0,0.06), 0 30px 60px -28px rgba(0,0,0,' + (0.35 + 0.2 * ativa) + ')', fontFamily: familia(el.fonte), color: el.corTexto || '#16171b',
477
+ outline: ativa > 0.05 ? (2 * ativa).toFixed(1) + 'px solid ' + alfa(acento, 0.9) : undefined,
478
+ opacity: t >= de ? 1 : 0, transform: 'translateY(' + ((1 - m) * 120 - ativa * 18) + 'px)' }}>
479
+ <div style={{ display: 'flex', alignItems: 'center', gap: cw * 0.05, marginBottom: cw * 0.1 }}>
480
+ <div style={{ minWidth: cw * 0.16, height: cw * 0.16, borderRadius: 99, background: i === 0 ? acento : '#f0f0f3', color: i === 0 ? sobre(acento) : '#16171b',
481
+ display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: cw * 0.075 }}>{c.numero ?? i + 1}</div>
482
+ <div style={{ fontFamily: MONO(el.fonteMono), fontSize: cw * 0.06, letterSpacing: '0.1em', color: '#6d7079', textTransform: 'uppercase' }}>{c.rotulo || ''}</div>
483
+ </div>
484
+ <div style={{ fontWeight: 800, fontSize: cw * (c.titulo && c.titulo.length > 30 ? 0.095 : 0.115), lineHeight: 1.12, letterSpacing: '-0.015em', marginBottom: cw * 0.08 }}>{c.titulo}</div>
485
+ {itens.map((it, j) => {
486
+ const dj = de + (j + 2) * 3 / fps;
487
+ return (
488
+ <div key={j} style={{ fontSize: cw * 0.062, lineHeight: 1.3, padding: cw * 0.05 + 'px 0', borderTop: '1px solid #ececf0', opacity: t >= dj ? 1 : 0,
489
+ background: c.marcado === j ? alfa(acento, 0.12) : undefined }}>{it}</div>
490
+ );
491
+ })}
492
+ </div>
493
+ );
494
+ })}
495
+ </div>
496
+ );
497
+ }
498
+
499
+ if (modo === 'mural') {
500
+ const cols = el.colunas || 7, lins = el.linhas || 5;
501
+ const cw = W / cols * 0.88;
502
+ const k = CURVAS_KIT.camera(entre(t, t0, t0 + (el.dur || 3.2)));
503
+ const cells = [];
504
+ for (let j = 0; j < lins; j++) {
505
+ for (let i = 0; i < cols; i++) {
506
+ const n = j * cols + i;
507
+ const de = t0 + (acaso(n + 2) * 0.6);
508
+ const c = lista[n % Math.max(1, lista.length)] || {};
509
+ const barras = 1 + Math.floor(acaso(n + 9) * 3);
510
+ cells.push(
511
+ <div key={n} style={{ position: 'absolute', left: i * (W / cols), top: j * (H / lins), width: cw, height: H / lins * 0.88, background: '#ffffff', borderRadius: 10,
512
+ boxShadow: '0 10px 24px -12px rgba(0,0,0,0.35)', padding: 10, boxSizing: 'border-box', opacity: t >= de ? 1 : 0,
513
+ transform: 'translateZ(' + (acaso(n + 5) * 40) + 'px)' }}>
514
+ <div style={{ fontFamily: MONO(el.fonteMono), fontSize: 13, color: '#8a8d96' }}>{c.rotulo || String(n + 1).padStart(2, '0')}</div>
515
+ {Array.from({ length: barras }).map((_, b) => (
516
+ <div key={b} style={{ height: 10, marginTop: 8, borderRadius: 3, width: (40 + acaso(n * 3 + b) * 55) + '%', background: b === 0 && acaso(n + 1) > 0.45 ? acento : '#e3e4e8' }} />
517
+ ))}
518
+ </div>,
519
+ );
520
+ }
521
+ }
522
+ return (
523
+ <div style={{ position: 'absolute', left: X, top: Y, width: W, height: H, perspective: 1800 }}>
524
+ <div style={{ position: 'absolute', inset: 0, transformStyle: 'preserve-3d', transformOrigin: '50% 50%',
525
+ transform: 'scale(' + lerp(el.de ?? 2.4, el.ate ?? 0.78, k) + ') rotateX(' + lerp(0, 26, k) + 'deg) rotateZ(' + lerp(0, -14, k) + 'deg)' }}>
526
+ {cells}
527
+ </div>
528
+ </div>
529
+ );
530
+ }
531
+
532
+ // voo: os cartões chegam do fundo do espaço e ficam flutuando, cada um inclinado.
533
+ const w = el.larguraCartao || Math.min(W, H) * 0.36;
534
+ return (
535
+ <div style={{ position: 'absolute', left: X, top: Y, width: W, height: H, perspective: 1400 }}>
536
+ {lista.map((c, i) => {
537
+ const px = c.x != null ? c.x : (0.08 + acaso(i + 1) * 0.84) * W;
538
+ const py = c.y != null ? c.y : (0.08 + acaso(i + 20) * 0.84) * H;
539
+ const de = t0 + (c.t != null ? c.t : i * 3 / fps);
540
+ const m = mola(t, de, fps, { damping: 24, stiffness: 150, mass: 0.8 });
541
+ const flutua = Math.sin(t * 1.1 + i * 1.9) * 10;
542
+ const rx = 14 + acaso(i + 3) * 18, ry = (acaso(i + 4) - 0.5) * 40, rz = (acaso(i + 5) - 0.5) * 18;
543
+ const z = -200 + acaso(i + 6) * 320;
544
+ return (
545
+ <div key={i} style={{ position: 'absolute', left: px - w / 2, top: py, opacity: t >= de ? Math.min(1, m * 3) : 0,
546
+ transform: 'translateZ(' + lerp(z - 900, z, m) + 'px) translateY(' + (flutua + (1 - m) * 160) + 'px) rotateX(' + rx + 'deg) rotateY(' + ry + 'deg) rotate(' + rz + 'deg)',
547
+ filter: z < -80 ? 'blur(' + ((-80 - z) / 60).toFixed(1) + 'px)' : undefined }}>
548
+ <Cartao c={c} w={w * (c.escala || 1)} el={el} destaque={c.destaque} />
549
+ </div>
550
+ );
551
+ })}
552
+ </div>
553
+ );
554
+ }
555
+
556
+ // ── o painel do produto ──────────────────────────────────────────────────
557
+ export function Painel({ el, t, fps }) {
558
+ const W = el.largura || 900;
559
+ const t0 = el.t || 0;
560
+ const m = mola(t, t0, fps, { damping: 26, stiffness: 160, mass: 0.8 });
561
+ const acento = el.corAcento || '#ff5a1f';
562
+ const esc = el.tema === 'escuro';
563
+ const bg = esc ? '#17181c' : '#ffffff';
564
+ const tinta = esc ? '#f3f3f5' : '#16171b';
565
+ const suaveCor = esc ? '#8d9099' : '#8a8d96';
566
+ // "escala" engorda tudo por dentro (letra, margem, botão) sem mudar a largura: é o painel de quem está em pé.
567
+ const u = (W / 100) * (el.escala || 1);
568
+ const texto = String(el.texto || '');
569
+ const dest = String(el.destaque || '');
570
+ const iD = dest ? texto.indexOf(dest) : -1;
571
+ const tMarca = t0 + (el.tMarca ?? 0.55);
572
+ const pMarca = CURVAS_KIT.arrasto(entre(t, tMarca, tMarca + 14 / fps));
573
+ const tSelo = t0 + (el.tSelo ?? 1.05);
574
+ const tBarras = t0 + (el.tBarras ?? 1.25);
575
+ const barras = el.barras || [];
576
+ const temLado = barras.length > 0;
577
+ // Empilhado (vídeo em pé), as barras vão embaixo do cartão, na largura toda.
578
+ const Wp = temLado && !el.empilhar ? W * 0.66 : W;
579
+ return (
580
+ <div style={{ position: 'absolute', left: el.x, top: el.y, width: W, perspective: 1600, opacity: t >= t0 ? 1 : 0 }}>
581
+ <div style={{ display: 'flex', flexDirection: el.empilhar ? 'column' : 'row', gap: u * 2.4, alignItems: 'flex-start', transformOrigin: '50% 100%',
582
+ transform: 'translateY(' + (1 - m) * 90 + 'px) rotateX(' + (1 - m) * 22 + 'deg)' }}>
583
+ <div style={{ position: 'relative', width: Wp, background: bg, borderRadius: u * 2.2, padding: u * 3, boxSizing: 'border-box', fontFamily: familia(el.fonte), color: tinta,
584
+ boxShadow: '0 2px 4px rgba(0,0,0,0.08), 0 40px 80px -30px rgba(0,0,0,0.4)' }}>
585
+ <div style={{ display: 'flex', alignItems: 'center', gap: u * 1.2, marginBottom: u * 2 }}>
586
+ <div style={{ width: u * 4, height: u * 4, borderRadius: '50%', background: tinta, color: bg, fontWeight: 800, fontSize: u * 2, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
587
+ {(String(el.titulo || '?').trim()[0] || '?').toUpperCase()}
588
+ </div>
589
+ <div style={{ fontWeight: 700, fontSize: u * 2.2 }}>{el.titulo || ''}</div>
590
+ <div style={{ marginLeft: 'auto', display: 'flex', gap: u * 1.4, fontSize: u * 1.5, color: suaveCor }}>{(el.abas || ['Prévia', 'Nota', 'Melhorar']).map((a) => <span key={a}>{a}</span>)}</div>
591
+ </div>
592
+ <div style={{ fontSize: u * (texto.length > 110 ? 2.5 : texto.length > 60 ? 3 : 4), lineHeight: 1.36, fontWeight: texto.length > 60 ? 500 : 600, minHeight: u * 10, letterSpacing: '-0.01em' }}>
593
+ {iD >= 0 ? (
594
+ <>
595
+ {texto.slice(0, iD)}
596
+ <span style={{ position: 'relative' }}>
597
+ <span style={{ position: 'absolute', left: '-0.08em', right: '-0.08em', top: '0.05em', bottom: '-0.02em', background: alfa(acento, 0.22), borderRadius: 4,
598
+ transform: 'scaleX(' + pMarca + ')', transformOrigin: '0% 50%' }} />
599
+ <span style={{ position: 'relative', color: pMarca > 0.5 ? mistura(tinta, acento, 0.55) : undefined }}>{dest}</span>
600
+ </span>
601
+ {texto.slice(iD + dest.length)}
602
+ </>
603
+ ) : texto}
604
+ </div>
605
+ <div style={{ display: 'flex', alignItems: 'center', gap: u * 1.2, marginTop: u * 3, fontSize: u * 1.6 }}>
606
+ <div style={{ color: suaveCor }}>Salvar</div>
607
+ <div style={{ marginLeft: 'auto', color: suaveCor }}>Rascunho</div>
608
+ <div style={{ background: tinta, color: bg, borderRadius: 99, padding: u * 0.7 + 'px ' + u * 1.6 + 'px', fontWeight: 700 }}>{el.botao || 'Agendar'}</div>
609
+ </div>
610
+ {el.selo ? (
611
+ <div style={{ position: 'absolute', right: -u * 2, top: -u * 2.6, background: acento, color: sobre(acento), borderRadius: 99, padding: u * 0.9 + 'px ' + u * 1.8 + 'px',
612
+ fontWeight: 800, fontSize: u * 1.8, boxShadow: '0 12px 24px -8px ' + alfa(acento, 0.7), whiteSpace: 'nowrap',
613
+ transform: 'scale(' + mola(t, tSelo, fps) + ') rotate(' + (1 - mola(t, tSelo, fps)) * -8 + 'deg)', transformOrigin: '80% 80%' }}>{'✓ ' + el.selo}</div>
614
+ ) : null}
615
+ </div>
616
+ {temLado ? (
617
+ <div style={{ width: el.empilhar ? W : W - Wp - u * 2.4, background: bg, borderRadius: u * 2, padding: u * 2.2, boxSizing: 'border-box', fontFamily: familia(el.fonte), color: tinta,
618
+ boxShadow: '0 2px 4px rgba(0,0,0,0.08), 0 30px 60px -28px rgba(0,0,0,0.4)', marginTop: el.empilhar ? 0 : u * 4 }}>
619
+ <div style={{ fontFamily: MONO(el.fonteMono), fontSize: u * 1.2, letterSpacing: '0.12em', color: suaveCor, marginBottom: u * 1.6, textTransform: 'uppercase' }}>{el.tituloBarras || 'Nota'}</div>
620
+ {barras.map((b, i) => {
621
+ const k = ED(entre(t, tBarras + i * 4 / fps, tBarras + i * 4 / fps + 0.7));
622
+ const v = Math.max(0, Math.min(100, Number(b.valor) || 0));
623
+ return (
624
+ <div key={i} style={{ marginBottom: u * 1.3 }}>
625
+ <div style={{ display: 'flex', fontSize: u * 1.35, marginBottom: u * 0.5 }}>
626
+ <span>{b.rotulo}</span><span style={{ marginLeft: 'auto', fontVariantNumeric: 'tabular-nums', color: suaveCor }}>{Math.round(v * k)}%</span>
627
+ </div>
628
+ <div style={{ height: u * 0.9, borderRadius: 99, background: esc ? '#2a2c32' : '#eeeff2', overflow: 'hidden' }}>
629
+ <div style={{ height: '100%', width: v * k + '%', background: acento, borderRadius: 99 }} />
630
+ </div>
631
+ </div>
632
+ );
633
+ })}
634
+ </div>
635
+ ) : null}
636
+ </div>
637
+ </div>
638
+ );
639
+ }
640
+
641
+ // ── a esfera de pontos ───────────────────────────────────────────────────
642
+ export function Esfera({ el, t, fps }) {
643
+ const R = el.raio || 320;
644
+ const t0 = el.t || 0;
645
+ const nasce = mola(t, t0, fps, { damping: 28, stiffness: 90, mass: 1 });
646
+ const lat = el.latitudes || 16, lon = el.longitudes || 28;
647
+ const gy = (t - t0) * 0.55, gx = 0.38;
648
+ const cor = el.cor || '#ffffff';
649
+ const acento = el.corAcento || '#ff5a1f';
650
+ const pts = [];
651
+ for (let i = 1; i < lat; i++) {
652
+ const th = (i / lat) * Math.PI;
653
+ for (let j = 0; j < lon; j++) {
654
+ const ph = (j / lon) * Math.PI * 2 + gy;
655
+ let x = Math.sin(th) * Math.cos(ph), y = Math.cos(th), z = Math.sin(th) * Math.sin(ph);
656
+ // inclina em X
657
+ const y2 = y * Math.cos(gx) - z * Math.sin(gx), z2 = y * Math.sin(gx) + z * Math.cos(gx);
658
+ const p = 1 / (1.6 - z2 * 0.45);
659
+ const k = i * lon + j;
660
+ const ac = acaso(k + 3) > 0.93;
661
+ pts.push(<circle key={k} cx={el.x + x * R * p * nasce} cy={el.y + y2 * R * p * nasce} r={(ac ? 3.2 : 2) * p * (z2 > 0 ? 1.15 : 0.8)}
662
+ fill={ac ? acento : cor} opacity={(0.25 + 0.75 * (z2 + 1) / 2) * nasce} />);
663
+ }
664
+ }
665
+ return (
666
+ <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', overflow: 'visible' }}>
667
+ <ellipse cx={el.x} cy={el.y} rx={R * 0.62 * nasce} ry={R * 0.62 * 0.36 * nasce} fill="none" stroke={alfa(cor, 0.12)} />
668
+ {pts}
669
+ </svg>
670
+ );
671
+ }
672
+
673
+ // ── o chão em perspectiva ────────────────────────────────────────────────
674
+ export function Grade({ el, t }) {
675
+ const W = el.largura || 1920, H = el.altura || 1080;
676
+ const hz = el.horizonte ?? H * 0.42;
677
+ const cor = el.cor || 'rgba(60,80,200,0.35)';
678
+ const linhas = [];
679
+ const cx = W / 2;
680
+ for (let i = -18; i <= 18; i++) linhas.push(<line key={'v' + i} x1={cx + i * 12} y1={hz} x2={cx + i * W * 0.16} y2={H * 1.4} stroke={cor} strokeWidth={1.2} />);
681
+ const off = (t * (el.velocidade ?? 0.35)) % 1;
682
+ for (let j = 0; j < 16; j++) {
683
+ const k = (j + off) / 16;
684
+ const y = hz + (H * 1.4 - hz) * k * k;
685
+ linhas.push(<line key={'h' + j} x1={0} y1={y} x2={W} y2={y} stroke={cor} strokeWidth={1.2} opacity={k} />);
686
+ }
687
+ return (
688
+ <svg style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: W, height: H, overflow: 'hidden',
689
+ WebkitMaskImage: 'linear-gradient(to bottom, transparent ' + (hz / H * 100) + '%, #000 ' + (hz / H * 100 + 30) + '%)',
690
+ maskImage: 'linear-gradient(to bottom, transparent ' + (hz / H * 100) + '%, #000 ' + (hz / H * 100 + 30) + '%)' }}>
691
+ {linhas}
692
+ </svg>
693
+ );
694
+ }
695
+
696
+ // ── a janela do site ─────────────────────────────────────────────────────
697
+ export function Site({ el, t, fps }) {
698
+ const W = el.largura || 1400, H = el.altura || 820;
699
+ const t0 = el.t || 0;
700
+ const m = mola(t, t0, fps, { damping: 28, stiffness: 120, mass: 0.9 });
701
+ const acento = el.corAcento || '#1f3cff';
702
+ const emPe = H > W; // a janela do celular: o herói vai embaixo da manchete
703
+ const u = (emPe ? W * 1.6 : W) / 100;
704
+ const heroi = emPe ? { left: u * 3, right: u * 3, top: H * 0.5, height: H * 0.44 } : { right: u * 3, top: H * 0.2, width: W * 0.36, height: H * 0.7 };
705
+ const texto = emPe ? { left: u * 3, top: H * 0.13, width: W - u * 6 } : { left: u * 4, top: H * 0.26, width: W * 0.5 };
706
+ return (
707
+ <div style={{ position: 'absolute', left: el.x, top: el.y, width: W, height: H, perspective: 1800, opacity: t >= t0 ? 1 : 0 }}>
708
+ <div style={{ position: 'absolute', inset: 0, background: '#ffffff', borderRadius: u * 2, overflow: 'hidden', fontFamily: familia(el.fonte), color: '#111216',
709
+ boxShadow: '0 2px 6px rgba(0,0,0,0.06), 0 60px 120px -40px rgba(0,0,0,0.35)', transformOrigin: '0% 50%',
710
+ transform: 'translateX(' + (1 - m) * 260 + 'px) rotateY(' + (1 - m) * -24 + 'deg)' }}>
711
+ <div style={{ display: 'flex', alignItems: 'center', padding: u * 2.6 + 'px ' + u * 4 + 'px', gap: u * 1.2 }}>
712
+ <Marca logo={el.logo} tamanho={u * 3.2} tinta={el.logo ? undefined : '#111216'} />
713
+ <div style={{ fontWeight: 800, fontSize: u * 2.4, letterSpacing: '-0.02em' }}>{el.marca || ''}</div>
714
+ <div style={{ marginLeft: 'auto', display: 'flex', gap: u * 2.6, fontSize: u * 1.35, color: '#3b3d45' }}>
715
+ {(el.menu || ['Início', 'Sobre', 'Serviços', 'Contato']).map((x) => <span key={x}>{x}</span>)}
716
+ </div>
717
+ </div>
718
+ {/* O herói: a manchete à esquerda, a marca grande à direita num bloco da cor. */}
719
+ <div style={{ position: 'absolute', ...texto, height: H * 0.6 }}>
720
+ <Manchete el={{ texto: el.titulo, x: 0, y: 0, largura: texto.width, tamanho: u * (String(el.titulo || '').length > 34 ? 4.6 : 5.6), fonte: el.fonte, peso: 800,
721
+ modo: 'desfoque', t: t0 + 0.45, passo: 3, cor: '#111216', corAcento: acento, destaque: el.destaque, entrelinha: 1.04 }} t={t} fps={fps} />
722
+ {el.sub ? <div style={{ position: 'absolute', top: emPe ? H * 0.2 : H * 0.34, fontSize: u * 1.6, lineHeight: 1.4, color: '#5a5d66', width: texto.width * 0.8, opacity: entre(t, t0 + 1.0, t0 + 1.3) }}>{el.sub}</div> : null}
723
+ <div style={{ position: 'absolute', top: emPe ? H * 0.27 : H * 0.46, display: 'inline-block', background: '#111216', color: '#fff', borderRadius: 99, padding: u * 1 + 'px ' + u * 2.2 + 'px',
724
+ fontWeight: 700, fontSize: u * 1.5, transform: 'scale(' + mola(t, t0 + 1.2, fps) + ')', transformOrigin: '0% 50%' }}>{el.botao || 'Fale com a gente'}</div>
725
+ </div>
726
+ <div style={{ position: 'absolute', ...heroi, borderRadius: u * 1.6, overflow: 'hidden',
727
+ background: 'radial-gradient(circle at 30% 25%, ' + mistura(acento, '#ffffff', 0.25) + ', ' + acento + ' 60%, ' + mistura(acento, '#000000', 0.25) + ')',
728
+ transform: 'translateY(' + (1 - mola(t, t0 + 0.25, fps)) * 60 + 'px)', opacity: t >= t0 + 0.25 ? 1 : 0 }}>
729
+ <div style={{ position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%,-50%) rotate(' + (-8 + 4 * Math.sin(t)) + 'deg)' }}>
730
+ <Marca logo={el.logo} tamanho={W * (emPe ? 0.36 : 0.16)} tinta={'linear-gradient(160deg, #ffffff, ' + mistura(acento, '#ffffff', 0.6) + ')'} />
731
+ </div>
732
+ </div>
733
+ </div>
734
+ </div>
735
+ );
736
+ }
737
+
738
+ export const MARCA = {
739
+ orbe: Orbe, vetor: Vetor, manchete: Manchete, cromo: Cromo, cores: Cores, padrao: Padrao, particulas: Particulas,
740
+ estrela: Estrela, cartoes: Cartoes, painel: Painel, esfera: Esfera, grade: Grade, site: Site,
741
+ };