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.
- package/README.md +30 -6
- package/bin/primocode.js +169 -342
- package/lib/act.js +360 -39
- package/lib/browser.js +105 -4
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/claude-engine.js +38 -17
- package/lib/codex-engine.js +27 -9
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +77 -11
- package/lib/estudio/cinema.js +27 -9
- package/lib/estudio/diretor.js +21 -1
- package/lib/estudio/ende.js +16 -5
- package/lib/estudio/estilo.js +66 -0
- package/lib/estudio/gerativo.js +1453 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/hibrido.js +165 -0
- package/lib/estudio/index.js +284 -53
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/gerativo.jsx +980 -0
- package/lib/estudio/kit/index.jsx +13 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/tres.jsx +308 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +78 -0
- package/lib/estudio/lancamento.js +441 -0
- package/lib/estudio/motion.js +2 -5
- package/lib/estudio/plano.js +28 -3
- package/lib/estudio/referencias.js +105 -0
- package/lib/estudio/remotion.js +139 -51
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/sons-leves.js +38 -1
- package/lib/estudio/variedade.js +105 -0
- package/lib/estudio/vox.js +414 -0
- package/lib/fala.js +1 -94
- package/lib/mcp.js +156 -0
- package/lib/personas.js +67 -0
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +44 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +100 -22
- package/lib/ui.js +50 -8
- package/lib/video/analise.js +89 -0
- package/lib/video/auto.js +329 -0
- package/lib/video/camadas.js +207 -0
- package/lib/video/cli.js +74 -0
- package/lib/video/delta.js +90 -0
- package/lib/video/edits.js +379 -0
- package/lib/video/filters.js +280 -0
- package/lib/video/fx3d/Camadas.jsx +64 -0
- package/lib/video/fx3d/Objetos.jsx +128 -0
- package/lib/video/fx3d/Root.jsx +18 -0
- package/lib/video/fx3d/modelos.jsx +304 -0
- package/lib/video/fx3d.js +108 -0
- package/lib/video/gerados.js +119 -0
- package/lib/video/objetos.js +111 -0
- package/lib/video/ops-fx.js +453 -0
- package/lib/video/ops.js +347 -0
- package/lib/video/paralelo.js +50 -0
- package/lib/video/pedido.js +191 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/rastrear.js +95 -0
- package/lib/video/rastro.js +276 -0
- package/lib/video/render.js +274 -0
- package/lib/video/texto.js +144 -0
- package/lib/video/timeline.js +318 -0
- package/lib/video/transcript.js +124 -0
- package/lib/voz.js +75 -223
- package/package.json +2 -3
- package/skills/motion-de-primeira/SKILL.md +5 -5
- package/studio/narrar.py +10 -7
- package/studio/rastrear.py +399 -0
- package/studio/texto.py +208 -0
- package/voz/primo_voz/__main__.py +96 -236
- package/voz/primo_voz/transcrever.py +31 -1
- package/app/ansi.js +0 -55
- package/app/app.css +0 -500
- package/app/app.js +0 -1017
- package/app/icone.svg +0 -6
- package/app/index.html +0 -220
- package/app/primo-marca.js +0 -192
- package/lib/app.js +0 -615
- package/lib/icone.js +0 -263
- package/lib/instalar-app.js +0 -231
- package/lib/painel.js +0 -291
- package/voz/primo_voz/desperta.py +0 -195
- 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
|
+
};
|