primocode 10.1.0 → 10.3.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/primocode.js +6 -26
- package/lib/act.js +5 -0
- package/lib/browser.js +1 -1
- package/lib/catalogo.js +17 -8
- package/lib/estudio/assets.js +75 -1
- package/lib/estudio/assinatura.js +108 -0
- package/lib/estudio/assinaturas/fluido.json +15 -0
- package/lib/estudio/assinaturas/gerativo.json +15 -0
- package/lib/estudio/batida.js +184 -0
- package/lib/estudio/biblioteca.js +52 -12
- package/lib/estudio/decisao.js +106 -0
- package/lib/estudio/diretor.js +70 -4
- package/lib/estudio/edicao.js +34 -16
- package/lib/estudio/efeitos.js +95 -0
- package/lib/estudio/estilo.js +17 -6
- package/lib/estudio/falantes.js +97 -0
- package/lib/estudio/fluido.js +110 -16
- package/lib/estudio/gerativo.js +309 -85
- package/lib/estudio/hibrido.js +1 -1
- package/lib/estudio/index.js +252 -45
- package/lib/estudio/kit/fluido.jsx +130 -9
- package/lib/estudio/kit/gerativo.jsx +90 -21
- package/lib/estudio/kit/marca.jsx +58 -1
- package/lib/estudio/kit/tres.jsx +4 -1
- package/lib/estudio/kit.js +2 -0
- package/lib/estudio/plano.js +5 -1
- package/lib/estudio/print-site.js +152 -0
- package/lib/estudio/referencias.js +47 -25
- package/lib/estudio/remotion.js +74 -3
- package/lib/estudio/revisor.js +117 -0
- package/lib/estudio/variedade.js +83 -21
- package/lib/video/filters.js +6 -1
- package/lib/video/ops.js +4 -3
- package/lib/video/timeline.js +1 -1
- package/lib/video/zoom.js +275 -0
- package/package.json +2 -2
- package/skills/motion-de-primeira/SKILL.md +3 -5
|
@@ -23,6 +23,7 @@ import { CURVAS_KIT, alfa } from './base';
|
|
|
23
23
|
const ed = CURVAS_KIT.editorial, cam = CURVAS_KIT.camera;
|
|
24
24
|
const c01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
|
|
25
25
|
const lerp = (a, b, k) => a + (b - a) * k;
|
|
26
|
+
const lum = (hex) => { const n = parseInt(String(hex).slice(1), 16); return (0.299 * (n >> 16 & 255) + 0.587 * (n >> 8 & 255) + 0.114 * (n & 255)) / 255; };
|
|
26
27
|
const entra = (t) => t * t;
|
|
27
28
|
const SANS = (f) => '"' + f + '", system-ui, sans-serif';
|
|
28
29
|
const SERIF = (f) => '"' + f + '", Georgia, serif';
|
|
@@ -52,7 +53,7 @@ function medir(t, tam, peso, serifa, P, mono) {
|
|
|
52
53
|
|
|
53
54
|
/* Um bloco de texto: quebra palavra a palavra (largura medida), cada linha
|
|
54
55
|
centrada em cx (ou alinhada à esquerda/direita), o bloco centrado em cy. */
|
|
55
|
-
function bloco(P, f, { tam, peso = 400, max = 1e9, cx, cy, alinha = 'centro', cor, mono = false }) {
|
|
56
|
+
function bloco(P, f, { tam, peso = 400, max = 1e9, cx, cy, alinha = 'centro', cor, corAcento = null, mono = false }) {
|
|
56
57
|
const esp = medir(' ', tam, peso, false, P, mono);
|
|
57
58
|
const ws = f.palavras.map((w) => ({ ...w, w: medir(w.t, w.acento ? tam * 1.14 : tam, peso, w.acento, P, mono) }));
|
|
58
59
|
const linhas = [[]];
|
|
@@ -67,7 +68,7 @@ function bloco(P, f, { tam, peso = 400, max = 1e9, cx, cy, alinha = 'centro', co
|
|
|
67
68
|
const top = cy - h / 2;
|
|
68
69
|
const x0 = alinha === 'centro' ? cx - w / 2 : alinha === 'esq' ? cx : cx - w;
|
|
69
70
|
return {
|
|
70
|
-
f, tam, peso, cor, esp, lh, w, h, top, cx, cy, mono, left: x0,
|
|
71
|
+
f, tam, peso, cor, corAcento, esp, lh, w, h, top, cx, cy, mono, left: x0,
|
|
71
72
|
linhas: linhas.map((l, i) => ({ palavras: l, y: top + i * lh, x: alinha === 'centro' ? cx - larg(l) / 2 : alinha === 'esq' ? cx : cx - larg(l) })),
|
|
72
73
|
};
|
|
73
74
|
}
|
|
@@ -108,6 +109,60 @@ function leiaute(P) {
|
|
|
108
109
|
ent = [0.3, 0.8];
|
|
109
110
|
}
|
|
110
111
|
const poe = (bl) => { out.blocos.push(bl); return bl; };
|
|
112
|
+
/* A IMAGEM (fluido.js escolhe a batida e o jeito): o texto se arruma em
|
|
113
|
+
volta dela, e o portador vira um ponto discreto enquanto a foto manda. */
|
|
114
|
+
if (b.imagem && pr) {
|
|
115
|
+
const j = b.imagem.jeito;
|
|
116
|
+
const t0 = Math.max(b.de + 0.05, Math.min(...b.frases.map((f) => f.palavras[0].em)));
|
|
117
|
+
const [sb] = fr('sub');
|
|
118
|
+
const branco = '#ffffff';
|
|
119
|
+
let foto, tY;
|
|
120
|
+
if (j === 'cartao') {
|
|
121
|
+
const cw = deitado ? W * 0.36 : W * 0.74, ch = deitado ? H * 0.62 : H * 0.42;
|
|
122
|
+
foto = { x: deitado ? cx + W * 0.2 : cx, y: deitado ? cy : cy + H * 0.12, w: cw, h: ch };
|
|
123
|
+
tY = deitado ? cy : cy - H * 0.24;
|
|
124
|
+
} else if (j === 'faixa') {
|
|
125
|
+
foto = { x: cx, y: deitado ? cy - H * 0.08 : cy - H * 0.06, w: W, h: deitado ? H * 0.46 : H * 0.34 };
|
|
126
|
+
tY = foto.y + foto.h / 2 + 130 * u;
|
|
127
|
+
} else { foto = { x: cx, y: cy, w: W, h: H }; tY = j === 'vazado' ? cy : cy + H * (deitado ? 0.22 : 0.2); }
|
|
128
|
+
const tx2 = j === 'cartao' && deitado ? cx - W * 0.2 : cx;
|
|
129
|
+
const sobreFoto = j === 'janela' || j === 'circulo';
|
|
130
|
+
let tam = (j === 'vazado' ? 200 : sobreFoto ? 92 : 76) * u;
|
|
131
|
+
if (j === 'vazado') {
|
|
132
|
+
const l100 = pr.palavras.reduce((s2, w) => s2 + medir(w.t, 100, 700, false, P), 0) + medir(' ', 100, 700, false, P) * (pr.palavras.length - 1);
|
|
133
|
+
tam = Math.min(360 * u, (W * 0.84 / (1 + 0.022 * (bi + 1))) / Math.max(1, l100) * 100);
|
|
134
|
+
}
|
|
135
|
+
const larg2 = j === 'cartao' && deitado ? W * 0.34 : W * 0.84;
|
|
136
|
+
const tb = poe(bloco(P, pr, { tam, peso: j === 'vazado' ? 700 : 400, max: larg2, cx: tx2, cy: tY, cor: sobreFoto ? branco : T, corAcento: sobreFoto ? branco : null }));
|
|
137
|
+
if (sb) poe(bloco(P, sb, { tam: 34 * u, max: larg2, cx: tx2, cy: tY + tb.h / 2 + 56 * u, cor: sobreFoto ? alfa(branco, 0.82) : S }));
|
|
138
|
+
out.imagem = { jeito: j, src: b.imagem.src, foto, t0, texto: tb };
|
|
139
|
+
// O portador: no círculo ele É a foto (cresce); nos outros, vira um ponto e some.
|
|
140
|
+
if (j === 'circulo') vai(Math.max(0, t0 - 0.5), t0 + 0.2, forma(cx, cy, 0, 0, { solid: 1, ac: 1, op: 0 }));
|
|
141
|
+
else vai(ent[0], ent[1], forma(tx2, tY - tb.h / 2 - 60 * u, 10 * u, 10 * u, { solid: 1, ac: 1, op: j === 'vazado' ? 1 : 0 }));
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
/* A PALAVRA GIGANTE (fluido.js marca): a frase curta enche a largura,
|
|
145
|
+
sem pílula, peso fino. Na VIRADA o portador cresce até cobrir a tela
|
|
146
|
+
de acento, e o texto troca para a cor que lê sobre ele. */
|
|
147
|
+
if (b.gigante && pr) {
|
|
148
|
+
const base = 100;
|
|
149
|
+
const larguraA100 = pr.palavras.reduce((s2, w) => s2 + medir(w.t, w.acento ? base * 1.14 : base, 300, w.acento, P), 0) + medir(' ', base, 300, false, P) * (pr.palavras.length - 1);
|
|
150
|
+
// Cabe já descontando o zoom que a câmera terá nesta batida (1 + 0,022 por batida) e a deriva.
|
|
151
|
+
const zoomAqui = 1 + 0.022 * (bi + 1);
|
|
152
|
+
const tam = Math.min(330 * u, Math.max(96 * u, (W * 0.8 / zoomAqui) / Math.max(1, larguraA100) * base));
|
|
153
|
+
const sobre = b.virada ? (lum(pal.acentos[0]) > 0.55 ? pal.fundo[0] : '#ffffff') : T;
|
|
154
|
+
const acentoSobre = b.virada ? sobre : pal.acentos[0];
|
|
155
|
+
const g = poe(bloco(P, pr, { tam, peso: 300, max: W * 0.84 / zoomAqui, cx, cy, cor: sobre, corAcento: acentoSobre }));
|
|
156
|
+
const [sb] = fr('sub');
|
|
157
|
+
if (sb) poe(bloco(P, sb, { tam: 34 * u, max: W * 0.7, cx, cy: cy + g.h / 2 + 70 * u, cor: b.virada ? sobre : S }));
|
|
158
|
+
// A virada começa com a PRIMEIRA frase que aparece (a voz pode dizer o sub antes do título).
|
|
159
|
+
const t0 = Math.max(b.de + 0.1, Math.min(...b.frases.map((f) => f.palavras[0].em)));
|
|
160
|
+
if (b.virada) {
|
|
161
|
+
const D = Math.hypot(W, H) * 1.08;
|
|
162
|
+
vai(Math.max(0, t0 - 0.55), t0 - 0.05, forma(cx, cy, D, D, { solid: 1, ac: 1 }));
|
|
163
|
+
} else vai(ent[0], ent[1], forma(cx, cy - g.h / 2 - 70 * u, 14 * u, 14 * u, { solid: 1, ac: 1 }));
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
111
166
|
switch (b.tipo) {
|
|
112
167
|
case 'abertura': {
|
|
113
168
|
const t = pr && poe(bloco(P, pr, { tam: (lado === 'centro' ? 96 : 80) * u, max: larg, cx: tx, cy: ty, cor: T }));
|
|
@@ -146,7 +201,9 @@ function leiaute(P) {
|
|
|
146
201
|
out.extra = { pilulas, tt };
|
|
147
202
|
// O portador vira a PRIMEIRA pílula; as outras nascem na ponta da curva.
|
|
148
203
|
const p0 = pilulas[0];
|
|
149
|
-
|
|
204
|
+
// A primeira pílula nasce com o primeiro item (antes, ficava vazia e enorme esperando a voz).
|
|
205
|
+
const i0 = p0 ? p0.f.palavras[0].em : b.de;
|
|
206
|
+
if (p0) vai(Math.max(ent[0], i0 - 0.4), Math.max(ent[1], i0 + 0.1), forma(p0.x, p0.y, p0.w, p0.h, { fill: 1, stroke: 0.35 }));
|
|
150
207
|
else vai(ent[0], ent[1], forma(cx, cy, 12 * u, 12 * u, { solid: 1, ac: 1 }));
|
|
151
208
|
break;
|
|
152
209
|
}
|
|
@@ -220,8 +277,12 @@ function leiaute(P) {
|
|
|
220
277
|
const h = umaLinha ? (t ? t.tam * 2 : 60 * u) : t.h;
|
|
221
278
|
if (sb) poe(umaLinha ? bloco(P, sb, { tam: 34 * u, max: larg, cx: tx, cy: ty + h / 2 + 62 * u, cor: S })
|
|
222
279
|
: bloco(P, sb, { tam: 34 * u, max: larg, cx: t.left, cy: ty + h / 2 + 50 * u, alinha: 'esq', cor: S }));
|
|
223
|
-
|
|
224
|
-
|
|
280
|
+
/* A pílula nasce com a primeira palavra, não antes: entrando em b.de,
|
|
281
|
+
ela ficava VAZIA até a voz chegar na frase (teste de 08/10). */
|
|
282
|
+
const p0 = pr ? pr.palavras[0].em : b.de;
|
|
283
|
+
const entT = [Math.max(ent[0], p0 - 0.4), Math.max(ent[1], p0 + 0.1)];
|
|
284
|
+
if (umaLinha) vai(entT[0], entT[1], forma(tx, ty, (t ? t.w : 200 * u) + 110 * u, h, { fill: 1, stroke: 0.35 }));
|
|
285
|
+
else vai(entT[0], entT[1], forma(t.left - 40 * u, ty, 6 * u, t.h * 0.92, { solid: 1, ac: 1, r: 3 }));
|
|
225
286
|
}
|
|
226
287
|
}
|
|
227
288
|
return out;
|
|
@@ -261,7 +322,7 @@ function Palavras({ bl, sai, t, P, op = 1, de = null }) {
|
|
|
261
322
|
filter: (1 - k) + o > 0.02 ? 'blur(' + ((1 - k) * 8 + o * 6).toFixed(2) + 'px)' : undefined,
|
|
262
323
|
fontFamily: w.acento ? SERIF(P.fontes.serifa) : bl.mono ? MONO(P.fontes.mono) : SANS(P.fontes.texto), fontStyle: w.acento ? 'italic' : 'normal',
|
|
263
324
|
fontWeight: w.acento ? 400 : bl.peso, fontSize: w.acento ? bl.tam * 1.14 : bl.tam, letterSpacing: w.acento || bl.mono ? 0 : '-0.02em',
|
|
264
|
-
color: w.acento ? pal.acentos[0] : bl.cor,
|
|
325
|
+
color: w.acento ? (bl.corAcento || pal.acentos[0]) : bl.cor,
|
|
265
326
|
}}>{w.t}</span>
|
|
266
327
|
);
|
|
267
328
|
})}
|
|
@@ -340,6 +401,55 @@ function Cartao({ c, ui, de: de0, sai, t, P, u: u0 }) {
|
|
|
340
401
|
);
|
|
341
402
|
}
|
|
342
403
|
|
|
404
|
+
// ── a foto: cinco jeitos de entrar, nenhum repetido no vídeo ────────────
|
|
405
|
+
function Foto({ im, b, sai, t, P, u, W, H }) {
|
|
406
|
+
const k = ed(c01((t - (im.t0 - 0.45)) / 0.75)); // a entrada
|
|
407
|
+
const o = entra(c01((t - sai) / 0.4)); // a saída
|
|
408
|
+
if (k <= 0.001 || o >= 0.999) return null;
|
|
409
|
+
const vida = c01((t - im.t0) / Math.max(1, b.ate - im.t0)); // o empurrão lento enquanto ela fica
|
|
410
|
+
const src = staticFile(im.src);
|
|
411
|
+
const f = im.foto;
|
|
412
|
+
const img = (extra = {}) => <Img src={src} style={{ position: 'absolute', left: 0, top: 0, width: '100%', height: '100%', objectFit: 'cover', ...extra }} />;
|
|
413
|
+
const escurece = <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg, rgba(0,0,0,0.05) 30%, rgba(0,0,0,0.62) 100%)' }} />;
|
|
414
|
+
if (im.jeito === 'janela') {
|
|
415
|
+
return <div style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, overflow: 'hidden', opacity: 1 - o,
|
|
416
|
+
clipPath: 'inset(0 ' + ((1 - k) * 100).toFixed(2) + '% 0 0)' }}>
|
|
417
|
+
{img({ transform: 'scale(' + (1.14 - 0.08 * vida) + ') translateX(' + ((1 - k) * -6) + '%)' })}{escurece}</div>;
|
|
418
|
+
}
|
|
419
|
+
if (im.jeito === 'circulo') {
|
|
420
|
+
const r = Math.hypot(W, H) * 0.55 * k * (1 - o);
|
|
421
|
+
return <div style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, overflow: 'hidden',
|
|
422
|
+
clipPath: 'circle(' + r.toFixed(1) + 'px at ' + W / 2 + 'px ' + H / 2 + 'px)' }}>
|
|
423
|
+
{img({ transform: 'scale(' + (1.2 - 0.12 * k - 0.05 * vida) + ')' })}{escurece}</div>;
|
|
424
|
+
}
|
|
425
|
+
if (im.jeito === 'cartao') {
|
|
426
|
+
const gira = (1 - k) * -38 + Math.sin(t * 0.6) * 3;
|
|
427
|
+
return <div style={{ position: 'absolute', left: f.x - f.w / 2, top: f.y - f.h / 2, width: f.w, height: f.h, borderRadius: 28 * u, overflow: 'hidden',
|
|
428
|
+
opacity: k * (1 - o), boxShadow: '0 ' + 50 * u + 'px ' + 90 * u + 'px -' + 30 * u + 'px rgba(0,0,0,0.55)',
|
|
429
|
+
transform: 'perspective(' + 1500 * u + 'px) translateY(' + ((1 - k) * 140 * u - o * 60 * u) + 'px) rotateY(' + gira + 'deg) rotateX(' + (6 * (1 - k)) + 'deg) scale(' + (0.86 + 0.14 * k) + ')' }}>
|
|
430
|
+
{img({ transform: 'scale(' + (1.08 - 0.06 * vida) + ')' })}</div>;
|
|
431
|
+
}
|
|
432
|
+
if (im.jeito === 'faixa') {
|
|
433
|
+
const meio = (1 - k + o) * 50;
|
|
434
|
+
return <div style={{ position: 'absolute', left: 0, top: f.y - f.h / 2, width: W, height: f.h, overflow: 'hidden',
|
|
435
|
+
clipPath: 'inset(' + meio.toFixed(2) + '% 0 ' + meio.toFixed(2) + '% 0)' }}>
|
|
436
|
+
{img({ transform: 'scale(1.12) translateX(' + (-4 + 8 * vida) + '%)' })}</div>;
|
|
437
|
+
}
|
|
438
|
+
// vazado: as letras do título, com a foto por dentro (SVG: o texto é o recorte).
|
|
439
|
+
const tb = im.texto;
|
|
440
|
+
const id = 'vz' + b.i;
|
|
441
|
+
const fam = '"' + P.fontes.texto + '", system-ui, sans-serif';
|
|
442
|
+
return <svg width={W} height={H} style={{ position: 'absolute', left: 0, top: 0, opacity: (1 - o) }}>
|
|
443
|
+
<defs><clipPath id={id}>{tb.linhas.map((l, li) => (
|
|
444
|
+
<text key={li} x={l.x} y={l.y + tb.lh * 0.8} fontFamily={fam} fontWeight={700} fontSize={tb.tam} letterSpacing={-0.02 * tb.tam}>
|
|
445
|
+
{l.palavras.map((w) => w.t).join(' ')}</text>))}</clipPath></defs>
|
|
446
|
+
<g clipPath={'url(#' + id + ')'} style={{ opacity: k }}>
|
|
447
|
+
<image href={src} x={-W * 0.06 * (1 - vida)} y={tb.top - tb.h * 0.6} width={W * 1.12} height={tb.h * 2.2} preserveAspectRatio="xMidYMid slice"
|
|
448
|
+
transform={'translate(0 ' + ((1 - k) * 40 * u) + ')'} />
|
|
449
|
+
</g>
|
|
450
|
+
</svg>;
|
|
451
|
+
}
|
|
452
|
+
|
|
343
453
|
// ── a peça ───────────────────────────────────────────────────────────────
|
|
344
454
|
export function Fluido({ el, t }) {
|
|
345
455
|
const P = el.partitura;
|
|
@@ -371,12 +481,13 @@ export function Fluido({ el, t }) {
|
|
|
371
481
|
const so = P.logo && ult.tipo === 'cta' ? 1 - ed(c01((t - ult.de - 0.65) / 0.5)) : 1;
|
|
372
482
|
|
|
373
483
|
const camadas = [];
|
|
374
|
-
L.batidas.forEach(({ b, blocos, extra, risca, cartao, comando }, i) => {
|
|
484
|
+
L.batidas.forEach(({ b, blocos, extra, risca, cartao, comando, imagem }, i) => {
|
|
375
485
|
const corte = b.transicao === 'corte' || (L.batidas[i + 1] && L.batidas[i + 1].b.transicao === 'corte');
|
|
376
486
|
const ultima = i === L.batidas.length - 1;
|
|
377
487
|
const sai = ultima ? 1e9 : b.ate - (corte ? 0 : 0.15), fimVis = b.ate + (corte ? 0 : 0.4);
|
|
378
488
|
if (t < b.de - 0.3 || t > fimVis || (corte && t >= b.ate && !ultima)) return;
|
|
379
489
|
if (cartao) camadas.push(<Cartao key={'k' + i} c={cartao} ui={b.ui} de={b.de} sai={sai} t={t} P={P} u={u} />);
|
|
490
|
+
if (imagem) camadas.push(<Foto key={'f' + i} im={imagem} b={b} sai={sai} t={t} P={P} u={u} W={W} H={H} />);
|
|
380
491
|
// A lista: as curvas que se desenham de uma pílula à próxima, e as pílulas que nascem na ponta.
|
|
381
492
|
if (b.tipo === 'lista' && extra) {
|
|
382
493
|
const ps = extra.pilulas;
|
|
@@ -398,17 +509,27 @@ export function Fluido({ el, t }) {
|
|
|
398
509
|
background: sup, border: (1.2 * u) + 'px solid ' + alfa(pal.tinta, 0.2), opacity: kp * o, transform: 'scale(' + (0.7 + 0.3 * kp) + ')',
|
|
399
510
|
boxShadow: pal.escuro ? undefined : '0 14px 34px -14px ' + alfa(pal.tinta, 0.3) }} />);
|
|
400
511
|
}
|
|
401
|
-
|
|
512
|
+
/* O marcador de cada pílula muda de vídeo para vídeo (P.marcador): o
|
|
513
|
+
brilho de quatro pontas em toda lista era vício (08/10). */
|
|
402
514
|
const kd = ed(c01((t - p.f.de) / 0.5));
|
|
403
|
-
|
|
515
|
+
const mk = P.marcador || 'brilho';
|
|
516
|
+
const mx0 = p.x - p.w / 2 + 34 * u, my0 = p.y - p.ic / 2;
|
|
517
|
+
if (mk === 'brilho') camadas.push(<svg key={'d' + i + j} width={p.ic} height={p.ic} viewBox="-1 -1 2 2" style={{ position: 'absolute', left: mx0, top: my0,
|
|
404
518
|
opacity: kd * o, transform: 'rotate(' + ((1 - kd) * -90 + Math.sin(t * 1.3 + j) * 6) + 'deg) scale(' + kd + ')' }}>
|
|
405
519
|
<path d={BRILHO4} fill={pal.acentos[0]} />
|
|
406
520
|
</svg>);
|
|
521
|
+
else if (mk === 'numero') camadas.push(<div key={'d' + i + j} style={{ position: 'absolute', left: mx0 - 4 * u, top: p.y - p.ic * 0.62, height: p.ic * 1.24, lineHeight: p.ic * 1.24 + 'px',
|
|
522
|
+
fontFamily: MONO(P.fontes.mono), fontSize: p.ic * 0.78, color: pal.acentos[0], opacity: kd * o, transform: 'translateY(' + (1 - kd) * 10 * u + 'px)' }}>{String(j + 1).padStart(2, '0')}</div>);
|
|
523
|
+
else if (mk === 'traco') camadas.push(<div key={'d' + i + j} style={{ position: 'absolute', left: mx0, top: p.y - 1.5 * u, width: p.ic * kd, height: 3 * u, borderRadius: 3,
|
|
524
|
+
background: pal.acentos[0], opacity: o }} />);
|
|
525
|
+
else if (mk === 'ponto') camadas.push(<div key={'d' + i + j} style={{ position: 'absolute', left: mx0 + p.ic * 0.3, top: p.y - p.ic * 0.2, width: p.ic * 0.4, height: p.ic * 0.4, borderRadius: 99,
|
|
526
|
+
background: pal.acentos[0], opacity: o, transform: 'scale(' + kd + ')' }} />);
|
|
407
527
|
});
|
|
408
528
|
}
|
|
409
529
|
for (const bl of blocos) {
|
|
410
530
|
const f = bl.f;
|
|
411
531
|
if (b.tipo === 'passos' && extra) continue;
|
|
532
|
+
if (imagem && imagem.jeito === 'vazado' && bl === imagem.texto) continue; // as letras são a foto (Foto desenha)
|
|
412
533
|
const op = f.papel === 'nao' && risca ? 1 - 0.55 * ed(c01((t - risca) / 0.4)) : 1;
|
|
413
534
|
if (f.papel === 'numero' && b.numero) {
|
|
414
535
|
const k = ed(c01((t - f.de) / 1.1));
|
|
@@ -284,6 +284,17 @@ function geometria(a, b, L, P) {
|
|
|
284
284
|
ponto e anda até onde o próximo traço começa) e os estados dela. */
|
|
285
285
|
function estadosDoAcento(P, L) {
|
|
286
286
|
const base = Math.min(P.largura, P.altura) / 1080;
|
|
287
|
+
/* O ACENTO EM TRAÇO (09/10, "todo vídeo tem aquela bolinha"): sem a semente
|
|
288
|
+
no meio da tela, sem a viagem em ponto e sem o recolher — cada traço se
|
|
289
|
+
desenha no seu lugar e some com a batida (acentoEm). */
|
|
290
|
+
const semBola = P.fundo && P.fundo.acento === 'traco';
|
|
291
|
+
if (semBola) {
|
|
292
|
+
const out = [];
|
|
293
|
+
P.batidas.forEach((b, bi) => b.acento.forEach((a) => out.push({ t: a.t, dur: a.dur, g: geometria(a, b, L, P), cor: b.cores.acento, batida: bi, forma: a.forma, ate: b.ate })));
|
|
294
|
+
out.sort((a, b) => a.t - b.t);
|
|
295
|
+
out.semBola = true;
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
287
298
|
const out = [{ t: 0, dur: 0.35, g: (() => { const r = 16 * base; const pts = []; for (let i = 0; i < N; i++) { const q = (i / (N - 1)) * Math.PI * 2; pts.push([P.largura / 2 + Math.cos(q) * r * 0.35, P.altura / 2 + Math.sin(q) * r * 0.35]); } return { pts, larg: r * 1.35, alfa: 1, frente: true, cx: P.largura / 2, cy: P.altura / 2 }; })(), cor: P.batidas[0].cores.acento, batida: 0 }];
|
|
288
299
|
P.batidas.forEach((b, bi) => {
|
|
289
300
|
b.acento.forEach((a, k) => {
|
|
@@ -295,12 +306,32 @@ function estadosDoAcento(P, L) {
|
|
|
295
306
|
}
|
|
296
307
|
out.push({ t: a.t, dur: a.dur, g, cor: b.cores.acento, batida: bi, forma: a.forma });
|
|
297
308
|
});
|
|
309
|
+
/* No fim da batida o acento se RECOLHE num ponto: ele ficava na última
|
|
310
|
+
forma até a batida seguinte, e a moldura sobrava sozinha na tela depois
|
|
311
|
+
que a palavra saía (teste de 08/10). */
|
|
312
|
+
const ult = b.acento[b.acento.length - 1];
|
|
313
|
+
if (ult && b.ate != null) {
|
|
314
|
+
const g = geometria(ult, b, L, P);
|
|
315
|
+
const t0 = Math.max(ult.t + (ult.dur || 0), b.ate - 0.3);
|
|
316
|
+
if (t0 < b.ate) out.push({ t: t0, dur: 0.25, g: geometria({ forma: 'viagem', x: g.cx, y: g.cy, r: 7 * base }, b, L, P), cor: b.cores.acento, batida: bi });
|
|
317
|
+
}
|
|
298
318
|
});
|
|
299
319
|
out.sort((a, b) => a.t - b.t);
|
|
300
320
|
return out;
|
|
301
321
|
}
|
|
302
322
|
|
|
303
323
|
function acentoEm(t, estados) {
|
|
324
|
+
if (estados.semBola) {
|
|
325
|
+
// O traço se desenha (os pontos aparecem em ordem) e some no fim da batida; antes do primeiro, nada.
|
|
326
|
+
let k = -1;
|
|
327
|
+
while (k < estados.length - 1 && estados[k + 1].t <= t) k++;
|
|
328
|
+
const cur = estados[k];
|
|
329
|
+
if (!cur) return { pts: [[0, 0], [0, 0]], larg: 0.1, alfa: 0, frente: true, cor: '#000000', cx: 0, cy: 0 };
|
|
330
|
+
const p = arrasto(c01((t - cur.t) / Math.max(0.01, cur.dur)));
|
|
331
|
+
const n = Math.max(2, Math.ceil(cur.g.pts.length * p));
|
|
332
|
+
const some = 1 - c01((t - (cur.ate - 0.22)) / 0.22);
|
|
333
|
+
return { pts: cur.g.pts.slice(0, n), larg: cur.g.larg, alfa: cur.g.alfa * some, frente: cur.g.frente, tampa: cur.g.tampa, cor: cur.cor, cx: cur.g.cx, cy: cur.g.cy };
|
|
334
|
+
}
|
|
304
335
|
let k = 0;
|
|
305
336
|
while (k < estados.length - 1 && estados[k + 1].t <= t) k++;
|
|
306
337
|
const cur = estados[k], prev = estados[Math.max(0, k - 1)];
|
|
@@ -650,7 +681,7 @@ function Fundo({ P, b, prevB, t, cam, frame, origem }) {
|
|
|
650
681
|
return (
|
|
651
682
|
<div style={{ position: 'absolute', inset: 0, background: corBase, overflow: 'hidden' }}>
|
|
652
683
|
{circulo}
|
|
653
|
-
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at ' + bx + '% ' + by + '%, ' + alfa(claro ? '#000000' : '#ffffff', 0.05) + ' 0%, transparent 60%)' }} />
|
|
684
|
+
{(f.ambiente || 'bokeh') !== 'limpo' && <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at ' + bx + '% ' + by + '%, ' + alfa(claro ? '#000000' : '#ffffff', 0.05) + ' 0%, transparent 60%)' }} />}
|
|
654
685
|
{(f.particulas || []).map((q, i) => {
|
|
655
686
|
const x = ((q.x - cam.x * q.z * 1.6 + t * q.v * (i % 2 ? 1 : -0.6)) % W + W) % W;
|
|
656
687
|
const y = ((q.y - cam.y * q.z * 1.6 - t * q.v * 0.7) % H + H) % H;
|
|
@@ -773,25 +804,39 @@ function Heroi({ b, t, P, slot }) {
|
|
|
773
804
|
return <svg width={W} height={H} style={{ position: 'absolute', left: 0, top: 0, overflow: 'visible', opacity: op }}>{els}</svg>;
|
|
774
805
|
}
|
|
775
806
|
|
|
776
|
-
/*
|
|
777
|
-
a
|
|
807
|
+
/* A MOLDURA (o HUD do motion designer): cantos de enquadramento, o assunto do
|
|
808
|
+
vídeo, a SEÇÃO ("01 · A CONVERSA"), o timecode e a régua do vídeo — fixa do
|
|
809
|
+
começo ao fim, como no reel de referência. Mora dentro da área segura
|
|
810
|
+
(P.hud.area: no 9:16, longe do status e dos botões do celular). 'cantos' é
|
|
811
|
+
a versão mínima: os cantos e a seção. Partitura antiga (sem área): a margem de antes. */
|
|
778
812
|
const SECOES = { gigante: 'TESE', pilha: 'ARGUMENTO', divisao: 'CONTRASTE', contador: 'NÚMERO', cascata: 'LISTA', parede: 'TEXTURA', citacao: 'CITAÇÃO', marca: 'MARCA', palavras: 'FALA', produto: 'PRODUTO' };
|
|
779
813
|
function HudG({ P, b, bi, t, fps }) {
|
|
780
|
-
const W = P.largura, H = P.altura,
|
|
814
|
+
const W = P.largura, H = P.altura, h = P.hud, deitado = W >= H;
|
|
815
|
+
const m0 = Math.round(Math.min(W, H) * 0.035);
|
|
816
|
+
const A = h.area || { x: m0, topo: m0, base: m0 };
|
|
817
|
+
const tam = Math.max(11, Math.round(Math.min(W, H) * (deitado ? 0.0125 : 0.018)));
|
|
818
|
+
const c = Math.round(Math.min(W, H) * 0.026);
|
|
781
819
|
const cor = alfa(b.cores.texto, 0.72);
|
|
782
820
|
const f = Math.round(t * fps);
|
|
783
821
|
const tc = [Math.floor(t / 60), Math.floor(t) % 60, f % fps].map((n) => String(n).padStart(2, '0')).join(':');
|
|
784
|
-
const rot = { position: 'absolute', fontFamily: familia(P.fontes.mono), fontSize: tam, letterSpacing: '0.14em', color: cor, whiteSpace: 'nowrap', textTransform: 'uppercase' };
|
|
785
|
-
const
|
|
822
|
+
const rot = { position: 'absolute', fontFamily: familia(P.fontes.mono), fontSize: tam, letterSpacing: '0.14em', color: cor, whiteSpace: 'nowrap', textTransform: 'uppercase', lineHeight: 1 };
|
|
823
|
+
const L = A.x, T = A.topo, R = W - A.x, B = H - A.base;
|
|
824
|
+
const canto = (x, y, sx, sy) => <div key={x + '-' + y} style={{ position: 'absolute', left: sx > 0 ? x : x - c, top: sy > 0 ? y : y - c, width: c, height: c,
|
|
786
825
|
borderLeft: sx > 0 ? '1.5px solid ' + cor : undefined, borderRight: sx < 0 ? '1.5px solid ' + cor : undefined, borderTop: sy > 0 ? '1.5px solid ' + cor : undefined, borderBottom: sy < 0 ? '1.5px solid ' + cor : undefined }} />;
|
|
826
|
+
// A seção: o número e o nome dela (a partitura antiga não tem seção — vale a composição).
|
|
827
|
+
const k = b.secao != null ? b.secao : bi;
|
|
828
|
+
const nome = (h.secoes || [])[k] || SECOES[b.comp] || String(b.comp).toUpperCase();
|
|
829
|
+
const secao = <div style={{ ...rot, right: W - R + c * 0.6, top: T + c * 0.6 }}>{String(k + 1).padStart(2, '0') + ' · ' + nome}</div>;
|
|
830
|
+
if (h.tipo === 'cantos') return <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>{canto(L, T, 1, 1)}{canto(R, T, -1, 1)}{canto(L, B, 1, -1)}{canto(R, B, -1, -1)}{secao}</div>;
|
|
831
|
+
const n = Math.max(1, (h.secoes || []).length);
|
|
787
832
|
return (
|
|
788
833
|
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
|
|
789
|
-
{canto(
|
|
790
|
-
<div style={{ ...rot, left:
|
|
791
|
-
|
|
792
|
-
<div style={{ ...rot, left:
|
|
793
|
-
<div style={{ ...rot, right:
|
|
794
|
-
<div style={{ position: 'absolute', left:
|
|
834
|
+
{canto(L, T, 1, 1)}{canto(R, T, -1, 1)}{canto(L, B, 1, -1)}{canto(R, B, -1, -1)}
|
|
835
|
+
<div style={{ ...rot, left: L + c * 0.6, top: T + c * 0.6 }}><b>{h.marca}</b>{deitado && <span style={{ opacity: 0.6 }}>{' · ' + h.ano}</span>}</div>
|
|
836
|
+
{secao}
|
|
837
|
+
<div style={{ ...rot, left: L + c * 0.6, bottom: H - B + c * 0.6 }}>{tc}<span style={{ opacity: 0.55 }}>{' ' + fps + ' FPS'}</span></div>
|
|
838
|
+
<div style={{ ...rot, right: W - R + c * 0.6, bottom: H - B + c * 0.6 }}>{(h.secoes ? String(k + 1).padStart(2, '0') + ' / ' + String(n).padStart(2, '0') : W + '×' + H)}</div>
|
|
839
|
+
<div style={{ position: 'absolute', left: L + c * 0.6, top: B - c * 0.35, height: 1.5, width: (R - L - c * 1.2) * c01(t / P.duracao), background: cor }} />
|
|
795
840
|
</div>
|
|
796
841
|
);
|
|
797
842
|
}
|
|
@@ -806,6 +851,9 @@ export function Gerativo({ el, t, fps }) {
|
|
|
806
851
|
if (!L) return <div style={{ position: 'absolute', inset: 0, background: P.paleta.fundo }} />;
|
|
807
852
|
const frame = Math.round(t * fps);
|
|
808
853
|
const bs = P.batidas;
|
|
854
|
+
/* O AMBIENTE (09/10): o bokeh e o feixe de luz só quando a identidade pede
|
|
855
|
+
(P.fundo.ambiente). Partitura antiga, sem o campo: como antes (ambiente || 'bokeh'). */
|
|
856
|
+
const ambiente = P.fundo.ambiente || 'bokeh', comLuz = ambiente !== 'limpo';
|
|
809
857
|
let bi = 0;
|
|
810
858
|
while (bi < bs.length - 1 && t >= bs[bi + 1].de) bi++;
|
|
811
859
|
const b = bs[bi];
|
|
@@ -914,18 +962,39 @@ export function Gerativo({ el, t, fps }) {
|
|
|
914
962
|
if (W - M - bl.x1 >= R * 2.3) slot = { cx: bl.x1 + (W - M - bl.x1) / 2, cy: (bl.y + bl.y1) / 2, R };
|
|
915
963
|
else if (bl.y - M * 1.4 >= R * 2.2) slot = { cx: W / 2, cy: M * 1.4 + (bl.y - M * 1.4) / 2, R };
|
|
916
964
|
else if (H - M * 1.4 - bl.y1 >= R * 2.2) slot = { cx: W / 2, cy: bl.y1 + (H - M * 1.4 - bl.y1) / 2, R };
|
|
917
|
-
|
|
965
|
+
// Sem vazio, não entra (09/10): o anel no meio do texto era enfeite que ninguém pediu.
|
|
966
|
+
else return null;
|
|
918
967
|
return <Heroi b={x} t={t} P={P} slot={slot} />;
|
|
919
968
|
})()}
|
|
920
969
|
<Textura b={x} cx={lay.caixas[x.linhas[0].id]} t={t} P={P} periodo={lay.textura} />
|
|
921
970
|
{x.icone3d && (() => {
|
|
922
|
-
/* O ícone 3D mora no vazio do bloco
|
|
923
|
-
(
|
|
971
|
+
/* O ícone 3D mora no vazio do bloco, no lugar que o projeto escolheu
|
|
972
|
+
(ic.lados, gerativo.js: direita, esquerda, acima, abaixo ou canto);
|
|
973
|
+
sem espaço lá, tenta o próximo; sem nenhum, não entra — nunca
|
|
974
|
+
por cima do texto. Partitura antiga (sem lados): direita ou acima. */
|
|
924
975
|
const ic = x.icone3d, tam = ic.tamanho, M = P.margem, bl = lay.bloco;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
976
|
+
const xTopo = x.bloco.alinha === 'centro' ? (bl.x + bl.x1) / 2 - tam / 2 : bl.x - tam * 0.08;
|
|
977
|
+
const fora = (qx, qy) => qx + tam <= bl.x - 8 || qx >= bl.x1 + 8 || qy + tam <= bl.y - 8 || qy >= bl.y1 + 8;
|
|
978
|
+
const LUGAR = {
|
|
979
|
+
'direita': () => (W - M - bl.x1 >= tam * 0.9 ? [Math.min(W - M - tam, bl.x1 + (W - M - bl.x1 - tam) / 2), (bl.y + bl.y1) / 2 - tam / 2] : null),
|
|
980
|
+
'esquerda': () => (bl.x - M >= tam * 0.9 ? [Math.max(M * 0.6, M + (bl.x - M - tam) / 2), (bl.y + bl.y1) / 2 - tam / 2] : null),
|
|
981
|
+
'acima': () => (bl.y - M * 1.2 >= tam * 0.85 ? [xTopo, Math.max(M, bl.y - tam - 0.02 * H)] : null),
|
|
982
|
+
'abaixo': () => (H - M * 1.2 - bl.y1 >= tam * 0.85 ? [xTopo, Math.min(H - M - tam, bl.y1 + 0.02 * H)] : null),
|
|
983
|
+
// O canto livre mais longe do texto: o ícone pequeno, de assinatura.
|
|
984
|
+
'canto': () => [[W - M - tam, M], [W - M - tam, H - M - tam], [M, M], [M, H - M - tam]].find(([qx, qy]) => fora(qx, qy)) || null,
|
|
985
|
+
};
|
|
986
|
+
const ordem = ic.lados || [ic.lado, 'acima'];
|
|
987
|
+
const achou = ordem.map((l) => LUGAR[l] && LUGAR[l]()).find(Boolean);
|
|
988
|
+
if (!achou) return null;
|
|
989
|
+
const [px, py] = achou;
|
|
990
|
+
/* O ÍCONE PLANO (09/10): o do Iconify que o roteiro pediu (o pão, o QR
|
|
991
|
+
code), na cor do acento da identidade — entra em mola e flutua de leve. */
|
|
992
|
+
if (ic.svg) {
|
|
993
|
+
const k = MOLA(t - ic.t), fl = Math.sin((t - ic.t) * 1.3) * 0.012 * tam, sai = c01((t - (x.ate - 0.25)) / 0.25);
|
|
994
|
+
return <div style={{ position: 'absolute', left: Math.round(px), top: Math.round(py + fl), width: tam, height: tam, color: ic.cor, opacity: c01(k * 1.4) * (1 - sai),
|
|
995
|
+
transform: 'translateZ(120px) scale(' + (0.6 + 0.4 * k).toFixed(4) + ') rotate(' + ((1 - k) * -8).toFixed(2) + 'deg)', transformOrigin: '50% 60%' }}
|
|
996
|
+
dangerouslySetInnerHTML={{ __html: ic.svg }} />;
|
|
997
|
+
}
|
|
929
998
|
return <div style={{ position: 'absolute', inset: 0, transform: 'translateZ(150px)' }}><Icone3D el={{ ...ic, x: Math.round(px), y: Math.round(py) }} t={t} /></div>;
|
|
930
999
|
})()}
|
|
931
1000
|
{x.linhas.map((l, li) => {
|
|
@@ -961,7 +1030,7 @@ export function Gerativo({ el, t, fps }) {
|
|
|
961
1030
|
</div>
|
|
962
1031
|
{/* A FRENTE: manchas de luz que passam perto da lente (bokeh) e o feixe
|
|
963
1032
|
de luz que varre o quadro quando uma batida chega. */}
|
|
964
|
-
{[0, 1, 2, 3].map((k) => {
|
|
1033
|
+
{ambiente === 'bokeh' && [0, 1, 2, 3].map((k) => {
|
|
965
1034
|
const per = 5 + acaso(P.semente % 997 + k * 7) * 4, fase = acaso(P.semente % 991 + k * 13) * per;
|
|
966
1035
|
const u = ((t + fase) % per) / per;
|
|
967
1036
|
const tam = Math.min(W, H) * (0.28 + acaso(k * 17 + 3) * 0.3);
|
|
@@ -970,7 +1039,7 @@ export function Gerativo({ el, t, fps }) {
|
|
|
970
1039
|
return <div key={'bk' + k} style={{ position: 'absolute', left: x - tam / 2, top: y - tam / 2, width: tam, height: tam, borderRadius: '50%', pointerEvents: 'none',
|
|
971
1040
|
background: 'radial-gradient(circle, ' + alfa(cor, k % 2 ? 0.16 : 0.08) + ' 0%, transparent 66%)', opacity: Math.sin(Math.PI * u) }} />;
|
|
972
1041
|
})}
|
|
973
|
-
{(() => {
|
|
1042
|
+
{comLuz && (() => {
|
|
974
1043
|
const k = c01((t - b.de) / 0.95);
|
|
975
1044
|
if (k <= 0 || k >= 1) return null;
|
|
976
1045
|
const x = lerp(-0.7 * W, 1.2 * W, ed(k));
|
|
@@ -735,7 +735,64 @@ export function Site({ el, t, fps }) {
|
|
|
735
735
|
);
|
|
736
736
|
}
|
|
737
737
|
|
|
738
|
+
// ── o print do site de verdade, em 3D ───────────────────────────────────
|
|
739
|
+
/* A peça `site` acima DESENHA um site genérico; esta mostra o print real
|
|
740
|
+
(print-site.js), numa janela de navegador ou num celular, inclinados em
|
|
741
|
+
perspectiva, com espessura, reflexo e a página rolando devagar.
|
|
742
|
+
"proporcao" é altura/largura do print: é ela que diz quanto há para rolar.
|
|
743
|
+
Só CSS 3D — o three.js entraria aqui só para o mesmo plano com textura. */
|
|
744
|
+
export function PrintSite({ el, t, fps }) {
|
|
745
|
+
const W = el.largura || 1280, H = el.altura || (el.celular ? 900 : 820);
|
|
746
|
+
const t0 = el.t || 0;
|
|
747
|
+
const cel = !!el.celular;
|
|
748
|
+
const m = mola(t, t0, fps, { damping: 30, stiffness: 90, mass: 1 });
|
|
749
|
+
const inc = el.inclinacao ?? (cel ? 14 : 18); // graus de giro em Y no repouso
|
|
750
|
+
const deriva = Math.sin((t - t0) * 0.6) * 3; // a câmera que respira, sem parar
|
|
751
|
+
const rotY = lerp(-48, -inc, m) + deriva;
|
|
752
|
+
const rotX = lerp(22, cel ? 6 : 9, m);
|
|
753
|
+
const z = lerp(-700, 0, m);
|
|
754
|
+
const barra = cel ? 0 : Math.round(H * 0.06); // a barra do navegador
|
|
755
|
+
const borda = cel ? Math.round(W * 0.045) : 0; // a moldura do celular
|
|
756
|
+
const telaW = W - borda * 2, telaH = H - barra - borda * 2;
|
|
757
|
+
// A página rola de cima até o fim, com respiro parado nas duas pontas.
|
|
758
|
+
const total = Math.max(0, telaW * (el.proporcao || 0) - telaH);
|
|
759
|
+
const dur = el.duracao || 6;
|
|
760
|
+
const k = el.rolar === false ? 0 : CURVAS_KIT.camera(entre(t, t0 + 1.0, t0 + dur - 0.6));
|
|
761
|
+
const brilho = 30 + 50 * entre(t, t0, t0 + dur);
|
|
762
|
+
const raio = cel ? Math.round(W * 0.14) : Math.round(H * 0.018);
|
|
763
|
+
return (
|
|
764
|
+
<div style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: W, height: H, perspective: 2400, opacity: t >= t0 ? Math.min(1, m * 2) : 0 }}>
|
|
765
|
+
<div style={{ position: 'absolute', inset: 0, transformStyle: 'preserve-3d',
|
|
766
|
+
transform: 'translateZ(' + z + 'px) rotateX(' + rotX + 'deg) rotateY(' + rotY + 'deg)' }}>
|
|
767
|
+
{/* A espessura: duas placas atrás, mais escuras — o aparelho tem corpo. */}
|
|
768
|
+
{[18, 9].map((d) => (
|
|
769
|
+
<div key={d} style={{ position: 'absolute', inset: 0, borderRadius: raio, background: d > 10 ? '#0b0c10' : '#1d1f26', transform: 'translateZ(' + -d + 'px)' }} />
|
|
770
|
+
))}
|
|
771
|
+
<div style={{ position: 'absolute', inset: 0, borderRadius: raio, overflow: 'hidden', background: cel ? '#0e0f13' : '#f4f4f6',
|
|
772
|
+
boxShadow: '0 80px 160px -40px rgba(0,0,0,0.55), 0 30px 60px -30px rgba(0,0,0,0.4)' }}>
|
|
773
|
+
{cel ? null : (
|
|
774
|
+
<div style={{ height: barra, display: 'flex', alignItems: 'center', gap: barra * 0.18, padding: '0 ' + barra * 0.4 + 'px', background: '#e9e9ee' }}>
|
|
775
|
+
{['#ff5f57', '#febc2e', '#28c840'].map((c) => <div key={c} style={{ width: barra * 0.24, height: barra * 0.24, borderRadius: 99, background: c }} />)}
|
|
776
|
+
<div style={{ marginLeft: barra * 0.4, flex: 1, maxWidth: W * 0.5, height: barra * 0.5, borderRadius: 99, background: '#fff', display: 'flex', alignItems: 'center',
|
|
777
|
+
padding: '0 ' + barra * 0.35 + 'px', fontFamily: MONO(), fontSize: barra * 0.26, color: '#55585f', whiteSpace: 'nowrap', overflow: 'hidden' }}>
|
|
778
|
+
{String(el.url || '').replace(/^https?:\/\//, '').replace(/\/$/, '')}
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
)}
|
|
782
|
+
<div style={{ position: 'absolute', left: borda, top: barra + borda, width: telaW, height: telaH, overflow: 'hidden', borderRadius: cel ? raio * 0.75 : 0, background: '#fff' }}>
|
|
783
|
+
<Img src={staticFile(el.src)} style={{ position: 'absolute', left: 0, top: 0, width: telaW, transform: 'translateY(' + (-total * k).toFixed(1) + 'px)' }} />
|
|
784
|
+
</div>
|
|
785
|
+
{cel ? <div style={{ position: 'absolute', left: '50%', top: borda * 1.3, width: W * 0.28, height: borda * 1.1, marginLeft: -W * 0.14, borderRadius: 99, background: '#0e0f13' }} /> : null}
|
|
786
|
+
{/* O reflexo que atravessa o vidro enquanto o aparelho gira. */}
|
|
787
|
+
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'none',
|
|
788
|
+
background: 'linear-gradient(115deg, rgba(255,255,255,0) ' + (brilho - 18) + '%, rgba(255,255,255,0.16) ' + brilho + '%, rgba(255,255,255,0) ' + (brilho + 18) + '%)' }} />
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
738
795
|
export const MARCA = {
|
|
739
796
|
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,
|
|
797
|
+
estrela: Estrela, cartoes: Cartoes, painel: Painel, esfera: Esfera, grade: Grade, site: Site, printsite: PrintSite,
|
|
741
798
|
};
|
package/lib/estudio/kit/tres.jsx
CHANGED
|
@@ -278,8 +278,11 @@ const FORMAS = {
|
|
|
278
278
|
};
|
|
279
279
|
|
|
280
280
|
function Mat({ tipo, cor }) {
|
|
281
|
-
// Metal escovado, bala (verniz) ou argila —
|
|
281
|
+
// Metal escovado, bala (verniz), pérola, acetinado ou argila — todos refletem a sala (Ambiente).
|
|
282
282
|
if (tipo === 'metal') return <meshPhysicalMaterial color={cor} roughness={0.32} metalness={0.85} clearcoat={0.4} envMapIntensity={1.2} />;
|
|
283
|
+
// Pérola (iridescente) e acetinado (meio metal, meio fosco): o cardápio de 09/10 (gerativo.MATERIAIS_ICONE).
|
|
284
|
+
if (tipo === 'perola') return <meshPhysicalMaterial color={cor} roughness={0.25} metalness={0.1} clearcoat={1} clearcoatRoughness={0.1} iridescence={1} iridescenceIOR={1.3} />;
|
|
285
|
+
if (tipo === 'acetinado') return <meshPhysicalMaterial color={cor} roughness={0.5} metalness={0.45} clearcoat={0.4} />;
|
|
283
286
|
if (tipo === 'verniz') return <meshPhysicalMaterial color={cor} roughness={0.18} metalness={0} clearcoat={1} clearcoatRoughness={0.08} sheen={0.3} />;
|
|
284
287
|
return <meshPhysicalMaterial color={cor} roughness={0.72} metalness={0} clearcoat={0.15} />; // argila: fosca, macia
|
|
285
288
|
}
|
package/lib/estudio/kit.js
CHANGED
|
@@ -49,6 +49,8 @@ const PECAS = {
|
|
|
49
49
|
esfera: {},
|
|
50
50
|
grade: {},
|
|
51
51
|
site: {},
|
|
52
|
+
// O print REAL do site (print-site.js) numa janela ou num celular em 3D, rolando.
|
|
53
|
+
printsite: { precisa: ['src'] },
|
|
52
54
|
// O produto em 3D (kit/tres.jsx): fone, celular, lata ou caixa, de geometria.
|
|
53
55
|
produto3d: { precisa: ['modelo'] },
|
|
54
56
|
// Os ícones 3D de geometria (kit/tres.jsx): raio, coração, bateria, relógio, fone…
|
package/lib/estudio/plano.js
CHANGED
|
@@ -54,7 +54,9 @@ const kit = require('./kit.js');
|
|
|
54
54
|
const TIPOS = new Set(['texto', 'forma', 'imagem', 'video', 'icone', 'legenda', 'componente', 'grupo', 'kit']);
|
|
55
55
|
const FORMAS = new Set(['retangulo', 'circulo', 'linha', 'poligono', 'traco']);
|
|
56
56
|
const AJUSTES = new Set(['cover', 'contain', 'preencher']);
|
|
57
|
-
const TRANSICOES = new Set(['corte', 'esmaecer', 'deslizar', 'zoom', 'giro', 'mascara', 'morph', 'chicote', 'soco', 'flash', 'liquido'
|
|
57
|
+
const TRANSICOES = new Set(['corte', 'esmaecer', 'deslizar', 'zoom', 'giro', 'mascara', 'morph', 'chicote', 'soco', 'flash', 'liquido',
|
|
58
|
+
// as 3D, que mexem nas duas cenas (remotion.js, estilo3d)
|
|
59
|
+
'virar', 'cubo', 'profundidade', 'inclinar']);
|
|
58
60
|
|
|
59
61
|
/* As propriedades que a animação por quadro-chave alcança. Cada uma é um
|
|
60
62
|
número no tempo; a curva entre dois quadros é a suavização declarada.
|
|
@@ -248,6 +250,8 @@ function conferirElemento(caminho, el, pasta, problemas, duracao) {
|
|
|
248
250
|
for (const oque of kit.conferirPeca(el)) problemas.push(erro(`${caminho}.peca`, oque));
|
|
249
251
|
// A logo de uma peça é arquivo da pasta, como a imagem.
|
|
250
252
|
if (el.logo) conferirArquivo(`${caminho}.logo`, el.logo, pasta, problemas);
|
|
253
|
+
// E o print do site (printsite) também: sem o arquivo, a janela 3D sai vazia.
|
|
254
|
+
if (el.src) conferirArquivo(`${caminho}.src`, el.src, pasta, problemas);
|
|
251
255
|
break;
|
|
252
256
|
}
|
|
253
257
|
}
|