primocode 8.35.0 → 8.37.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/lib/act.js +68 -0
- package/lib/browser.js +292 -1
- package/lib/catalogo.js +11 -2
- package/lib/claude-engine.js +25 -2
- package/lib/codex-engine.js +9 -2
- package/lib/pipeline.js +447 -0
- package/lib/studio.js +16 -3
- package/lib/tools.js +2 -1
- package/package.json +2 -2
- package/studio/ferramentas/corte.md +173 -10
- package/studio/server.py +107 -4
- package/studio/web/js/analise.js +319 -11
- package/studio/web/js/audio.js +367 -55
- package/studio/web/js/render.js +352 -3
- package/studio/web/js/video.js +4 -0
- package/studio/web/render.css +227 -3
package/studio/web/js/render.js
CHANGED
|
@@ -4,6 +4,60 @@
|
|
|
4
4
|
Dois modos: layout nomeado (o estúdio posiciona) ou "livre" (x/y/l/a).
|
|
5
5
|
========================================================================== */
|
|
6
6
|
|
|
7
|
+
/* ── A QUEBRA DA LEGENDA ─────────────────────────────────────────────────
|
|
8
|
+
* Legenda de vídeo tem regra de ofício, e ela não é opinião: linha curta o
|
|
9
|
+
* bastante para o olho pegar de relance (~38 caracteres em 1080p), no máximo
|
|
10
|
+
* três linhas, e a quebra caindo em espaço — nunca no meio da palavra.
|
|
11
|
+
*
|
|
12
|
+
* E as linhas saem EQUILIBRADAS. Encher a primeira até o talo e deixar duas
|
|
13
|
+
* palavras na segunda é o que faz a legenda parecer torta mesmo estando
|
|
14
|
+
* centralizada — foi essa a reclamação.
|
|
15
|
+
*/
|
|
16
|
+
var LEGENDA_LARGURA = 38;
|
|
17
|
+
|
|
18
|
+
/** Quebra gulosa numa largura dada: enche a linha até não caber mais. */
|
|
19
|
+
function envolver(palavras, largura) {
|
|
20
|
+
var linhas = [];
|
|
21
|
+
var atual = "";
|
|
22
|
+
for (var i = 0; i < palavras.length; i++) {
|
|
23
|
+
var tenta = atual ? atual + " " + palavras[i] : palavras[i];
|
|
24
|
+
if (atual && tenta.length > largura) { linhas.push(atual); atual = palavras[i]; }
|
|
25
|
+
else atual = tenta;
|
|
26
|
+
}
|
|
27
|
+
if (atual) linhas.push(atual);
|
|
28
|
+
return linhas;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function quebrarLegenda(texto) {
|
|
32
|
+
var t = String(texto == null ? "" : texto).replace(/\s+/g, " ").trim();
|
|
33
|
+
if (!t) return "";
|
|
34
|
+
if (t.length <= LEGENDA_LARGURA) return t;
|
|
35
|
+
|
|
36
|
+
var palavras = t.split(" ");
|
|
37
|
+
var quantas = envolver(palavras, LEGENDA_LARGURA).length;
|
|
38
|
+
|
|
39
|
+
/* O EQUILÍBRIO.
|
|
40
|
+
*
|
|
41
|
+
* A quebra gulosa enche a primeira linha até o talo e larga duas palavras na
|
|
42
|
+
* última — é isso que faz a legenda parecer torta mesmo estando
|
|
43
|
+
* centralizada, e foi essa a reclamação.
|
|
44
|
+
*
|
|
45
|
+
* A primeira tentativa foi dividir o comprimento pelo número de linhas e
|
|
46
|
+
* cortar no alvo. Não serve: o resto se acumula na última, que estourava a
|
|
47
|
+
* largura (41 caracteres numa régua de 38).
|
|
48
|
+
*
|
|
49
|
+
* O que serve é apertar a régua: procurar a MENOR largura que ainda cabe no
|
|
50
|
+
* mesmo número de linhas. Aí nenhuma linha passa do teto — a régua só
|
|
51
|
+
* encolheu — e todas ficam parecidas, porque foi o aperto que as igualou.
|
|
52
|
+
*/
|
|
53
|
+
var melhor = LEGENDA_LARGURA;
|
|
54
|
+
for (var w = LEGENDA_LARGURA - 1; w >= 12; w--) {
|
|
55
|
+
if (envolver(palavras, w).length > quantas) break;
|
|
56
|
+
melhor = w;
|
|
57
|
+
}
|
|
58
|
+
return envolver(palavras, melhor).join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
7
61
|
var PAPEIS = {
|
|
8
62
|
chapeu: { classe: "chapeu" },
|
|
9
63
|
titulo: { tamanho: 84, peso: 660, altura: 1.06 },
|
|
@@ -61,6 +115,13 @@ function aplicarFundo(no, fundo) {
|
|
|
61
115
|
// Era metade do "os vídeos ficaram todos iguais".
|
|
62
116
|
if (/^(malha|brilho|grade|pontos|suave|vinheta|aurora|nevoa|ondas|raios|xadrez)$/.test(fundo)) {
|
|
63
117
|
no.setAttribute("data-fundo", fundo);
|
|
118
|
+
} else if (fundo === "marca") {
|
|
119
|
+
/* A cor da casa, chapada e sem efeito nenhum. Num anúncio de marca é o
|
|
120
|
+
fundo mais usado de todos — e escrevê-lo como `"#21C25E"` obrigaria a
|
|
121
|
+
repetir o hexadecimal em cada cena, que é como uma delas acaba com a
|
|
122
|
+
cor de ontem depois de a marca mudar de tom. */
|
|
123
|
+
no.style.background = "var(--c-tom)";
|
|
124
|
+
no.setAttribute("data-fundo", "marca");
|
|
64
125
|
} else no.style.background = fundo;
|
|
65
126
|
return;
|
|
66
127
|
}
|
|
@@ -160,11 +221,30 @@ function svgGrafico(e, l, a) {
|
|
|
160
221
|
|
|
161
222
|
/* -------------------------------------------------------------- elementos */
|
|
162
223
|
|
|
224
|
+
/* O KIT DE ANÚNCIO — os quatro últimos da lista abaixo.
|
|
225
|
+
*
|
|
226
|
+
* Nasceram de um vídeo de referência mandado pelo dono — um comercial de
|
|
227
|
+
* fintech — com uma frase só: "ainda não tá bom, tá genérico. Quero nesse
|
|
228
|
+
* nível."
|
|
229
|
+
*
|
|
230
|
+
* Medindo a peça de hoje contra aquela, o que faltava não era tema nem cor:
|
|
231
|
+
* tema claro e `marca.cor` já existiam e resolvem o mundo da marca. O que não
|
|
232
|
+
* existia era o PRODUTO. Um anúncio de cartão mostra o cartão, mostra o app
|
|
233
|
+
* funcionando dentro do celular, mostra a linha de produtos e assina no fim.
|
|
234
|
+
* Com o vocabulário antigo, "o cartão" era um retângulo verde com a palavra
|
|
235
|
+
* "cartão" escrita dentro — e é exatamente isso que se lê como genérico.
|
|
236
|
+
*
|
|
237
|
+
* ── A LISTA ABAIXO É LIDA POR UM TESTE, e por isso não tem comentário dentro.
|
|
238
|
+
* O `vocabulario.test.js` recorta este objeto com uma expressão regular e
|
|
239
|
+
* pega as palavras: qualquer texto entre as chaves vira "tipo que o servidor
|
|
240
|
+
* precisa aceitar", e o teste passa a cobrar `fintech`, `genérico` e `dono`.
|
|
241
|
+
* Comentário sobre os tipos mora AQUI FORA. */
|
|
163
242
|
var TIPOS_CONHECIDOS = { texto: 1, lista: 1, cartoes: 1, numeros: 1, tabela: 1, codigo: 1,
|
|
164
243
|
citacao: 1, forma: 1, imagem: 1, icone: 1, grafico: 1, no: 1, conector: 1,
|
|
165
244
|
objeto3d: 1, clipe: 1,
|
|
166
245
|
selo: 1, progresso: 1, avatar: 1, dispositivo: 1, conversa: 1, passos: 1,
|
|
167
|
-
opcoes: 1
|
|
246
|
+
opcoes: 1,
|
|
247
|
+
cartao: 1, app: 1, vitrine: 1, assinatura: 1 };
|
|
168
248
|
/* Os sete últimos são o vocabulário do vídeo de referência: pílula de estado,
|
|
169
249
|
trilha de progresso, foto de pessoa, celular/notebook com conteúdo dentro,
|
|
170
250
|
balões de conversa, etapas com uma acesa, e pergunta com alternativas.
|
|
@@ -481,7 +561,15 @@ function renderElemento(e, ctx) {
|
|
|
481
561
|
if (e.peso || papel.peso) no.style.fontWeight = e.peso || papel.peso;
|
|
482
562
|
if (papel.altura) no.style.lineHeight = papel.altura;
|
|
483
563
|
no.style.color = e.cor || papel.cor || "";
|
|
484
|
-
|
|
564
|
+
/* LEGENDA DE FALA É SEMPRE CENTRALIZADA, e o `alinhar` do modelo não
|
|
565
|
+
manda nela. Legenda de vídeo alinhada à esquerda não existe em lugar
|
|
566
|
+
nenhum — ela é lida em movimento, e o olho volta para o meio da tela.
|
|
567
|
+
Bastava o modelo escrever `alinhar: "esquerda"` uma vez para a legenda
|
|
568
|
+
sair torta, e ele escrevia. */
|
|
569
|
+
if (e.alinhar && e.papel !== "fala") {
|
|
570
|
+
no.style.textAlign = e.alinhar;
|
|
571
|
+
no.style.alignItems = e.alinhar === "centro" ? "center" : (e.alinhar === "direita" ? "flex-end" : "flex-start");
|
|
572
|
+
}
|
|
485
573
|
if (e.fonte === "mono") no.style.fontFamily = "var(--f-mono)";
|
|
486
574
|
if (e.fonte === "display") no.style.fontFamily = "var(--f-display)";
|
|
487
575
|
if (e.maiusculas) no.style.textTransform = "uppercase";
|
|
@@ -489,7 +577,26 @@ function renderElemento(e, ctx) {
|
|
|
489
577
|
if (e.altura != null) no.style.lineHeight = e.altura;
|
|
490
578
|
if (e.estilo) no.setAttribute("data-estilo", e.estilo);
|
|
491
579
|
|
|
492
|
-
|
|
580
|
+
/* A LEGENDA SE QUEBRA SOZINHA.
|
|
581
|
+
"A IA não consegue formatar bem" — e não vai conseguir: ela escreve a
|
|
582
|
+
frase, não a diagramação. Quem sabe quantos caracteres cabem numa
|
|
583
|
+
linha de 1080p é este código, e é ele que quebra.
|
|
584
|
+
|
|
585
|
+
Sem isto a legenda saía como parágrafo: cinco linhas subindo pela
|
|
586
|
+
tela, tapando o rosto de quem fala. */
|
|
587
|
+
var corpoTexto = _rico(e.papel === "fala" ? quebrarLegenda(e.texto) : e.texto);
|
|
588
|
+
if (e.papel === "fala" && !e.tamanho) {
|
|
589
|
+
/* Legenda longa ENCOLHE em vez de crescer para cima. Três linhas com o
|
|
590
|
+
corpo de uma linha comem metade do quadro, e o rosto de quem fala
|
|
591
|
+
fica atrás delas.
|
|
592
|
+
|
|
593
|
+
Não se corta palavra: perder o que foi dito é pior que letra menor.
|
|
594
|
+
O piso de 62% é onde a letra ainda se lê num celular. */
|
|
595
|
+
var nLinhas = String(quebrarLegenda(e.texto)).split("\n").length;
|
|
596
|
+
var base = (PAPEIS.fala.tamanho || 52) * (ctx.escalaTipo || 1);
|
|
597
|
+
var fator = Math.max(0.62, 1 - (nLinhas - 1) * 0.10);
|
|
598
|
+
if (nLinhas > 1) no.style.fontSize = _px(Math.round(base * fator));
|
|
599
|
+
}
|
|
493
600
|
|
|
494
601
|
// marcador e fita precisam envolver cada linha para o fundo acompanhar
|
|
495
602
|
if (e.estilo === "marcador" || e.estilo === "fita") {
|
|
@@ -844,10 +951,232 @@ function renderElemento(e, ctx) {
|
|
|
844
951
|
if (typeof e.de === "number") vid.dataset.de = e.de;
|
|
845
952
|
if (typeof e.ate === "number") vid.dataset.ate = e.ate;
|
|
846
953
|
no.style.borderRadius = _px(e.raio != null ? e.raio : 0);
|
|
954
|
+
|
|
955
|
+
/* ── SÓ A VOZ, COM A TELA MONTADA POR CIMA ────────────────────────
|
|
956
|
+
*
|
|
957
|
+
* "Não deixa opção de manter só a voz com uma tela de exemplos."
|
|
958
|
+
*
|
|
959
|
+
* Faltava mesmo, e não era teimosia do agente: o vocabulário tinha
|
|
960
|
+
* `mudo` (imagem sem som) e NÃO tinha o contrário. Sem isto, usar a
|
|
961
|
+
* gravação como narração — a voz da pessoa por cima de telas montadas
|
|
962
|
+
* pelo estúdio — era impossível de escrever, por mais que se pedisse.
|
|
963
|
+
*
|
|
964
|
+
* O <video> continua no lugar, carregando e tocando: é ele que leva o
|
|
965
|
+
* áudio para o grafo da trilha (e daí para o MP4) e é o `de`/`ate` dele
|
|
966
|
+
* que dá a duração da cena. O que muda é que ele não aparece — some da
|
|
967
|
+
* tela e some do quadro exportado, que é o `data-som` lido no
|
|
968
|
+
* `video.js`. Um <video> com `display:none` também continuaria tocando,
|
|
969
|
+
* mas alguns navegadores param de decodificar o que não está no
|
|
970
|
+
* layout, e sem quadro decodificado o player o trata como não-pronto. */
|
|
971
|
+
if (e.apenas === "som" || e.som === true) {
|
|
972
|
+
vid.dataset.som = "1";
|
|
973
|
+
no.style.position = "absolute";
|
|
974
|
+
no.style.left = "0"; no.style.top = "0";
|
|
975
|
+
no.style.width = "1px"; no.style.height = "1px";
|
|
976
|
+
no.style.opacity = "0";
|
|
977
|
+
no.style.pointerEvents = "none";
|
|
978
|
+
no.style.overflow = "hidden";
|
|
979
|
+
}
|
|
847
980
|
no.appendChild(vid);
|
|
848
981
|
break;
|
|
849
982
|
}
|
|
850
983
|
|
|
984
|
+
/* ── O CARTÃO ────────────────────────────────────────────────────────
|
|
985
|
+
Um produto desenhado, não um retângulo com o nome dele escrito dentro.
|
|
986
|
+
O que faz o olho ler "cartão" são quatro detalhes pequenos e nenhum
|
|
987
|
+
deles é o formato: o CHIP, o símbolo de aproximação, a numeração
|
|
988
|
+
mascarada e o pé com nome e validade. Sem eles o desenho é uma caixa;
|
|
989
|
+
com eles, qualquer pessoa reconhece em um quadro de vídeo.
|
|
990
|
+
|
|
991
|
+
A cor sai da marca (`--c-tom`) quando ninguém diz outra, para o cartão
|
|
992
|
+
pertencer à peça em vez de ser mais um objeto colorido nela. */
|
|
993
|
+
case "cartao": {
|
|
994
|
+
var cc = e.cor || "var(--c-tom)";
|
|
995
|
+
no.style.setProperty("--cc", cc);
|
|
996
|
+
if (e.largura) no.style.width = _px(e.largura);
|
|
997
|
+
if (e.movimento) no.setAttribute("data-mov", e.movimento);
|
|
998
|
+
if (e.face === "verso") no.setAttribute("data-face", "verso");
|
|
999
|
+
|
|
1000
|
+
var corpo = document.createElement("div");
|
|
1001
|
+
corpo.className = "cartao-corpo";
|
|
1002
|
+
|
|
1003
|
+
var topo = document.createElement("div");
|
|
1004
|
+
topo.className = "cartao-topo";
|
|
1005
|
+
var marca = document.createElement("span");
|
|
1006
|
+
marca.className = "cartao-marca";
|
|
1007
|
+
marca.textContent = e.nome || "";
|
|
1008
|
+
var vari = document.createElement("span");
|
|
1009
|
+
vari.className = "cartao-variante";
|
|
1010
|
+
vari.textContent = e.variante || "";
|
|
1011
|
+
topo.appendChild(marca); topo.appendChild(vari);
|
|
1012
|
+
|
|
1013
|
+
var meio = document.createElement("div");
|
|
1014
|
+
meio.className = "cartao-meio";
|
|
1015
|
+
var chip = document.createElement("i");
|
|
1016
|
+
chip.className = "cartao-chip";
|
|
1017
|
+
// O símbolo de aproximação: três arcos. Em SVG porque em texto ele
|
|
1018
|
+
// depende de uma fonte que a máquina pode não ter.
|
|
1019
|
+
var onda = document.createElement("span");
|
|
1020
|
+
onda.className = "cartao-onda";
|
|
1021
|
+
onda.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true">'
|
|
1022
|
+
+ '<path d="M8 5a10 10 0 0 1 0 14M13 8a5.6 5.6 0 0 1 0 8M17.6 2.4a15 15 0 0 1 0 19.2"'
|
|
1023
|
+
+ ' fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
|
|
1024
|
+
meio.appendChild(chip); meio.appendChild(onda);
|
|
1025
|
+
|
|
1026
|
+
var num = document.createElement("div");
|
|
1027
|
+
num.className = "cartao-numero";
|
|
1028
|
+
// Os quatro últimos dígitos e mais nada: é o que aparece num cartão de
|
|
1029
|
+
// verdade em foto de campanha, e é o que não vira dado de ninguém.
|
|
1030
|
+
num.textContent = "•••• •••• •••• " + String(e.numero || "4821").slice(-4);
|
|
1031
|
+
|
|
1032
|
+
var pe = document.createElement("div");
|
|
1033
|
+
pe.className = "cartao-pe";
|
|
1034
|
+
var quem = document.createElement("span");
|
|
1035
|
+
quem.textContent = e.titular || "SEU NOME AQUI";
|
|
1036
|
+
var val = document.createElement("span");
|
|
1037
|
+
val.textContent = e.validade || "12/29";
|
|
1038
|
+
pe.appendChild(quem); pe.appendChild(val);
|
|
1039
|
+
|
|
1040
|
+
var brilho = document.createElement("i");
|
|
1041
|
+
brilho.className = "cartao-brilho";
|
|
1042
|
+
|
|
1043
|
+
corpo.appendChild(brilho);
|
|
1044
|
+
corpo.appendChild(topo);
|
|
1045
|
+
corpo.appendChild(meio);
|
|
1046
|
+
corpo.appendChild(num);
|
|
1047
|
+
corpo.appendChild(pe);
|
|
1048
|
+
no.appendChild(corpo);
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/* ── A TELA DO APP ───────────────────────────────────────────────────
|
|
1053
|
+
O `dispositivo` já desenhava o aparelho; o que ele mostrava DENTRO era
|
|
1054
|
+
texto centralizado no meio do vidro, que não se parece com aplicativo
|
|
1055
|
+
nenhum. O que faz uma tela parecer um app é a ARRUMAÇÃO: cabeçalho em
|
|
1056
|
+
cima, um número grande, linhas com o nome à esquerda e o valor à
|
|
1057
|
+
direita separadas por fio, e um botão cheio embaixo.
|
|
1058
|
+
|
|
1059
|
+
Vale sozinho na cena e vale dentro do `dispositivo` — é o mesmo
|
|
1060
|
+
elemento, e por isso a tela do celular e a do print da campanha não
|
|
1061
|
+
divergem. */
|
|
1062
|
+
case "app": {
|
|
1063
|
+
if (e.escuro) no.setAttribute("data-escuro", "1");
|
|
1064
|
+
if (e.topo) {
|
|
1065
|
+
var cab = document.createElement("div");
|
|
1066
|
+
cab.className = "app-topo";
|
|
1067
|
+
cab.innerHTML = "<span>" + _fugir(e.topo) + "</span><i></i>";
|
|
1068
|
+
no.appendChild(cab);
|
|
1069
|
+
}
|
|
1070
|
+
if (e.valor) {
|
|
1071
|
+
var caixaV = document.createElement("div");
|
|
1072
|
+
caixaV.className = "app-valor";
|
|
1073
|
+
if (e.rotulo) {
|
|
1074
|
+
var rot = document.createElement("small");
|
|
1075
|
+
rot.textContent = e.rotulo;
|
|
1076
|
+
caixaV.appendChild(rot);
|
|
1077
|
+
}
|
|
1078
|
+
var vv = document.createElement("strong");
|
|
1079
|
+
vv.textContent = e.valor;
|
|
1080
|
+
caixaV.appendChild(vv);
|
|
1081
|
+
no.appendChild(caixaV);
|
|
1082
|
+
}
|
|
1083
|
+
var linhas = e.linhas || e.itens || [];
|
|
1084
|
+
if (linhas.length) {
|
|
1085
|
+
var lista = document.createElement("div");
|
|
1086
|
+
lista.className = "app-linhas";
|
|
1087
|
+
linhas.slice(0, 8).forEach(function (li) {
|
|
1088
|
+
/* Aceita ["nome", "valor"] e "nome|valor" — o segundo é a forma que
|
|
1089
|
+
todo o resto da spec usa (lista, cartoes, passos), e quem escreve
|
|
1090
|
+
o documento vai chutar a que já conhece. */
|
|
1091
|
+
var nome = li, valor = "";
|
|
1092
|
+
if (Array.isArray(li)) { nome = li[0]; valor = li[1] || ""; }
|
|
1093
|
+
else if (typeof li === "string" && li.indexOf("|") > -1) {
|
|
1094
|
+
var par = li.split("|"); nome = par[0]; valor = par.slice(1).join("|");
|
|
1095
|
+
}
|
|
1096
|
+
var linha = document.createElement("div");
|
|
1097
|
+
linha.className = "app-linha";
|
|
1098
|
+
linha.innerHTML = "<span>" + _fugir(String(nome).trim()) + "</span>"
|
|
1099
|
+
+ "<b>" + _fugir(String(valor).trim()) + "</b>";
|
|
1100
|
+
lista.appendChild(linha);
|
|
1101
|
+
});
|
|
1102
|
+
no.appendChild(lista);
|
|
1103
|
+
}
|
|
1104
|
+
if (e.botao) {
|
|
1105
|
+
var bt = document.createElement("div");
|
|
1106
|
+
bt.className = "app-botao";
|
|
1107
|
+
bt.textContent = e.botao;
|
|
1108
|
+
no.appendChild(bt);
|
|
1109
|
+
}
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/* ── A VITRINE ───────────────────────────────────────────────────────
|
|
1114
|
+
A linha de produtos, com um escolhido. O `cartoes` genérico resolvia
|
|
1115
|
+
isso com três caixinhas de vidro do tamanho de um selo; aqui os itens
|
|
1116
|
+
são CARTÕES, o atual vem à frente e os outros recuam — que é o desenho
|
|
1117
|
+
de toda tela de "escolha o seu". */
|
|
1118
|
+
case "vitrine": {
|
|
1119
|
+
var itens = (e.itens || []).slice(0, 5);
|
|
1120
|
+
var atual = e.atual != null ? Number(e.atual) : 0;
|
|
1121
|
+
if (e.titulo) {
|
|
1122
|
+
var tt = document.createElement("div");
|
|
1123
|
+
tt.className = "vitrine-titulo";
|
|
1124
|
+
tt.textContent = e.titulo;
|
|
1125
|
+
no.appendChild(tt);
|
|
1126
|
+
}
|
|
1127
|
+
var fila = document.createElement("div");
|
|
1128
|
+
fila.className = "vitrine-fila";
|
|
1129
|
+
itens.forEach(function (it, i) {
|
|
1130
|
+
var dado = typeof it === "string"
|
|
1131
|
+
? { nome: it.split("|")[0], detalhe: it.split("|").slice(1).join("|") }
|
|
1132
|
+
: (it || {});
|
|
1133
|
+
var mini = renderElemento({
|
|
1134
|
+
tipo: "cartao", nome: e.nome || dado.marca || "",
|
|
1135
|
+
variante: dado.nome || dado.variante || "",
|
|
1136
|
+
cor: dado.cor || e.cor, numero: dado.numero,
|
|
1137
|
+
titular: " ", validade: " ",
|
|
1138
|
+
}, ctx);
|
|
1139
|
+
var col = document.createElement("div");
|
|
1140
|
+
col.className = "vitrine-item" + (i === atual ? " atual" : "");
|
|
1141
|
+
col.appendChild(mini);
|
|
1142
|
+
if (dado.detalhe) {
|
|
1143
|
+
var d = document.createElement("small");
|
|
1144
|
+
d.textContent = dado.detalhe;
|
|
1145
|
+
col.appendChild(d);
|
|
1146
|
+
}
|
|
1147
|
+
fila.appendChild(col);
|
|
1148
|
+
});
|
|
1149
|
+
no.appendChild(fila);
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/* ── A ASSINATURA ────────────────────────────────────────────────────
|
|
1154
|
+
O fim do anúncio. Toda peça de marca fecha assim — o logo sozinho,
|
|
1155
|
+
grande, sobre a cor da casa, e nada mais na tela. Sem este elemento a
|
|
1156
|
+
última cena do vídeo era mais uma frase, e a peça acabava sem assinar
|
|
1157
|
+
quem falou. */
|
|
1158
|
+
case "assinatura": {
|
|
1159
|
+
no.setAttribute("data-sobre", /^(marca|claro|escuro)$/.test(e.sobre || "") ? e.sobre : "marca");
|
|
1160
|
+
if (e.logo) {
|
|
1161
|
+
var img2 = document.createElement("img");
|
|
1162
|
+
img2.className = "assinatura-logo";
|
|
1163
|
+
img2.src = e.logo; img2.alt = e.nome || "";
|
|
1164
|
+
no.appendChild(img2);
|
|
1165
|
+
} else if (e.nome) {
|
|
1166
|
+
var nm = document.createElement("div");
|
|
1167
|
+
nm.className = "assinatura-nome";
|
|
1168
|
+
nm.textContent = e.nome;
|
|
1169
|
+
no.appendChild(nm);
|
|
1170
|
+
}
|
|
1171
|
+
if (e.frase) {
|
|
1172
|
+
var fr = document.createElement("div");
|
|
1173
|
+
fr.className = "assinatura-frase";
|
|
1174
|
+
fr.textContent = e.frase;
|
|
1175
|
+
no.appendChild(fr);
|
|
1176
|
+
}
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
851
1180
|
case "icone": {
|
|
852
1181
|
var t = e.tamanho || 90;
|
|
853
1182
|
no.style.width = _px(e.l || t);
|
|
@@ -1081,6 +1410,11 @@ function renderCena(cena, doc, op) {
|
|
|
1081
1410
|
elementos = elementos.filter(function (e) {
|
|
1082
1411
|
if (!e || e.x != null || e.y != null) return true;
|
|
1083
1412
|
if (e.tipo === "clipe") { deChao.push(e); return false; }
|
|
1413
|
+
/* A ASSINATURA É A CENA INTEIRA, e por isso entra pelo chão e não pelo
|
|
1414
|
+
fluxo. Pelo fluxo ela vira uma caixinha colorida no meio da tela, com a
|
|
1415
|
+
margem do layout em volta — que é o oposto de assinar: o logo tem de
|
|
1416
|
+
encostar nas bordas do quadro, como em qualquer fim de comercial. */
|
|
1417
|
+
if (e.tipo === "assinatura") { deChao.push(e); return false; }
|
|
1084
1418
|
// Legenda de fala mora no terço de baixo, como toda legenda de vídeo.
|
|
1085
1419
|
// Pelo fluxo ela iria para o meio da tela, em cima do rosto de quem fala.
|
|
1086
1420
|
if (e.tipo === "texto" && e.papel === "fala") { falas.push(e); return false; }
|
|
@@ -1096,6 +1430,21 @@ function renderCena(cena, doc, op) {
|
|
|
1096
1430
|
// tela vertical pede tipografia maior: os padrões são calibrados em 1920x1080
|
|
1097
1431
|
var escalaTipo = H > W ? 1.35 : 1;
|
|
1098
1432
|
no.style.setProperty("--esc", escalaTipo);
|
|
1433
|
+
// A altura do quadro, para o CSS dimensionar o que precisa ocupar a cena
|
|
1434
|
+
// (o aparelho, hoje) em proporção e não em pixels calibrados num formato só.
|
|
1435
|
+
no.style.setProperty("--alto", H + "px");
|
|
1436
|
+
/* A UNIDADE DA CENA: 1% da largura do quadro.
|
|
1437
|
+
|
|
1438
|
+
Sem ela, tudo o que o CSS escreve em `em` cai na fonte herdada — que numa
|
|
1439
|
+
cena é a do navegador, ~14px — e o desenho colapsa sem erro nenhum. Foi
|
|
1440
|
+
medido num quadro de 1080x1920: a linha do extrato saiu com 7,84px e o
|
|
1441
|
+
nome da assinatura com 30,8px, num quadro de 1920 de altura. Na tela
|
|
1442
|
+
aquilo não é "pequeno": é invisível.
|
|
1443
|
+
|
|
1444
|
+
O resto do sistema resolve isso mandando PIXEL do documento (`tamanho`).
|
|
1445
|
+
Peça que não tem `tamanho` — a tela do app, a vitrine, a assinatura — se
|
|
1446
|
+
mede por aqui, e assim acompanha o formato em vez de ser calibrada num só. */
|
|
1447
|
+
no.style.setProperty("--u", (W / 100) + "px");
|
|
1099
1448
|
var ctx = {
|
|
1100
1449
|
i: 0, escalaTipo: escalaTipo,
|
|
1101
1450
|
animPadrao: op.animar ? (cena.anim || "subir") : null,
|
package/studio/web/js/video.js
CHANGED
|
@@ -192,6 +192,10 @@ var VideoExport = (function () {
|
|
|
192
192
|
// para desenhar levanta exceção em alguns navegadores e mancha o
|
|
193
193
|
// canvas em outros.
|
|
194
194
|
if (v.readyState < 2 || !v.videoWidth) return;
|
|
195
|
+
// Clipe de SÓ ÁUDIO: entra pelo som e não pelo quadro. Ele tem 1px na
|
|
196
|
+
// tela, então sem esta linha o export desenharia uma tira de um pixel
|
|
197
|
+
// esticada por cima da cena montada — visível, e sem causa aparente.
|
|
198
|
+
if (v.dataset && v.dataset.som === "1") return;
|
|
195
199
|
var r = v.getBoundingClientRect();
|
|
196
200
|
var dx = (r.left - caixaCena.left) * escX;
|
|
197
201
|
var dy = (r.top - caixaCena.top) * escY;
|
package/studio/web/render.css
CHANGED
|
@@ -511,8 +511,17 @@
|
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
.el.dispositivo[data-forma="celular"] { width: 100%; }
|
|
514
|
+
/* O TETO DO APARELHO ACOMPANHA O QUADRO. Ele era 780px fixos, calibrados em
|
|
515
|
+
1920x1080 — e num vídeo vertical de 1080x1920 isso deixa o celular ocupando
|
|
516
|
+
um terço da altura, boiando no meio do branco. Num anúncio o aparelho é o
|
|
517
|
+
assunto da cena: ele ocupa a tela. `--alto` é a altura da cena, posta pelo
|
|
518
|
+
renderCena. */
|
|
514
519
|
.el.dispositivo[data-forma="celular"] .moldura {
|
|
515
|
-
|
|
520
|
+
/* Altura em PIXEL vindo do quadro, sem porcentagem: `min(100%, …)` dentro de
|
|
521
|
+
um pai de altura automática é percentagem circular — o navegador desiste
|
|
522
|
+
dela e o aparelho volta a ser medido pela largura. Medido: 408px de altura
|
|
523
|
+
num quadro de 1920, um quinto da tela. */
|
|
524
|
+
height: calc(var(--alto, 1080px) * .62); width: auto; max-width: 100%;
|
|
516
525
|
aspect-ratio: 9 / 19;
|
|
517
526
|
border-radius: 42px; padding: 12px;
|
|
518
527
|
}
|
|
@@ -761,8 +770,14 @@
|
|
|
761
770
|
.cena.animar .el[data-an="cortina"] { animation-name: anCortina; animation-duration: .85s; }
|
|
762
771
|
.cena.animar .el[data-an="elastico"] { animation-name: anElastico; animation-duration: .95s; }
|
|
763
772
|
|
|
764
|
-
/* palavra a palavra — cada palavra entra sozinha
|
|
765
|
-
|
|
773
|
+
/* palavra a palavra — cada palavra entra sozinha.
|
|
774
|
+
|
|
775
|
+
A palavra nasce VISÍVEL, e quem a esconde é a animação (com `both`, o
|
|
776
|
+
estado inicial do keyframe já vale antes de ela começar). Com `opacity: 0`
|
|
777
|
+
aqui na base, toda cena com `anim: "palavra"` saía EM BRANCO fora do player
|
|
778
|
+
— na miniatura, na prévia parada do editor, em qualquer render estático.
|
|
779
|
+
Texto que some sem erro nenhum é o defeito mais caro deste arquivo. */
|
|
780
|
+
.el.txt .pal { display: inline-block; }
|
|
766
781
|
.cena.animar .el[data-an="palavra"] { animation: none; opacity: 1; }
|
|
767
782
|
.cena.animar .el[data-an="palavra"] .pal {
|
|
768
783
|
animation: anSubir .58s cubic-bezier(.22, 1, .36, 1) both;
|
|
@@ -933,3 +948,212 @@
|
|
|
933
948
|
display: grid; place-items: center; min-height: 0; overflow: visible;
|
|
934
949
|
}
|
|
935
950
|
.cena .fluxo .lado-imagem.com-aparelho > .el { max-height: 100%; }
|
|
951
|
+
|
|
952
|
+
/* ══ O KIT DE ANÚNCIO ═══════════════════════════════════════════════════════
|
|
953
|
+
Nasceu de um vídeo de referência mandado pelo dono — um comercial de
|
|
954
|
+
fintech — com uma frase só: "ainda não tá bom, tá genérico. Quero nesse
|
|
955
|
+
nível." Medindo a peça de hoje contra aquela, o que faltava não era tema
|
|
956
|
+
nem cor (tema claro e `marca.cor` já existiam): era o PRODUTO.
|
|
957
|
+
══════════════════════════════════════════════════════════════════════════ */
|
|
958
|
+
|
|
959
|
+
/* ── cartão ───────────────────────────────────────────────────────────────
|
|
960
|
+
A proporção é a de verdade (85,6 × 54 mm = 1,586). Ela importa mais que o
|
|
961
|
+
desenho: um retângulo na proporção errada não é lido como cartão por mais
|
|
962
|
+
chip que tenha. */
|
|
963
|
+
.el.cartao {
|
|
964
|
+
--cc: var(--c-tom);
|
|
965
|
+
width: min(100%, 620px);
|
|
966
|
+
perspective: 1400px;
|
|
967
|
+
}
|
|
968
|
+
.el.cartao .cartao-corpo {
|
|
969
|
+
position: relative;
|
|
970
|
+
aspect-ratio: 1.586;
|
|
971
|
+
border-radius: 6.2%;
|
|
972
|
+
padding: 7% 7.5%;
|
|
973
|
+
display: flex; flex-direction: column; justify-content: space-between;
|
|
974
|
+
color: #fff;
|
|
975
|
+
/* Duas fontes de luz e uma sombra longa: é o que separa "objeto pousado na
|
|
976
|
+
cena" de "retângulo colado por cima". */
|
|
977
|
+
background:
|
|
978
|
+
radial-gradient(120% 140% at 12% 4%, rgba(255, 255, 255, .30), transparent 52%),
|
|
979
|
+
linear-gradient(145deg,
|
|
980
|
+
color-mix(in srgb, var(--cc) 88%, #fff),
|
|
981
|
+
var(--cc) 46%,
|
|
982
|
+
color-mix(in srgb, var(--cc) 72%, #000) 100%);
|
|
983
|
+
box-shadow:
|
|
984
|
+
inset 0 1px 0 rgba(255, 255, 255, .38),
|
|
985
|
+
inset 0 -1px 0 rgba(0, 0, 0, .22),
|
|
986
|
+
0 2.2% 5% -1% rgba(0, 0, 0, .45);
|
|
987
|
+
transform: rotate(-1.6deg);
|
|
988
|
+
}
|
|
989
|
+
/* O reflexo diagonal. Sem ele o plástico vira papel colorido. */
|
|
990
|
+
.el.cartao .cartao-brilho {
|
|
991
|
+
position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
|
|
992
|
+
background: linear-gradient(102deg, transparent 34%, rgba(255, 255, 255, .20) 46%,
|
|
993
|
+
rgba(255, 255, 255, .05) 54%, transparent 62%);
|
|
994
|
+
}
|
|
995
|
+
.el.cartao .cartao-topo {
|
|
996
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 1em;
|
|
997
|
+
}
|
|
998
|
+
.el.cartao .cartao-marca {
|
|
999
|
+
font-weight: 800; font-size: 6.4cqw; letter-spacing: -.01em;
|
|
1000
|
+
}
|
|
1001
|
+
.el.cartao .cartao-variante {
|
|
1002
|
+
font-size: 3.4cqw; letter-spacing: .18em; text-transform: uppercase;
|
|
1003
|
+
opacity: .82; font-weight: 600;
|
|
1004
|
+
}
|
|
1005
|
+
.el.cartao .cartao-meio { display: flex; align-items: center; gap: 4%; }
|
|
1006
|
+
/* O chip: dourado, com os contatos. É o detalhe que mais faz o desenho ser
|
|
1007
|
+
reconhecido — some ele e sobra um cartão de visita. */
|
|
1008
|
+
.el.cartao .cartao-chip {
|
|
1009
|
+
width: 13%; aspect-ratio: 1.32; border-radius: 14%;
|
|
1010
|
+
background: linear-gradient(150deg, #f7e3a1, #d9b558 46%, #b8912f);
|
|
1011
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18);
|
|
1012
|
+
position: relative; flex: none;
|
|
1013
|
+
}
|
|
1014
|
+
.el.cartao .cartao-chip::before,
|
|
1015
|
+
.el.cartao .cartao-chip::after {
|
|
1016
|
+
content: ""; position: absolute; left: 12%; right: 12%; height: 1px;
|
|
1017
|
+
background: rgba(0, 0, 0, .28);
|
|
1018
|
+
}
|
|
1019
|
+
.el.cartao .cartao-chip::before { top: 36%; }
|
|
1020
|
+
.el.cartao .cartao-chip::after { top: 64%; }
|
|
1021
|
+
.el.cartao .cartao-onda { width: 7%; opacity: .9; display: block; flex: none; }
|
|
1022
|
+
.el.cartao .cartao-onda svg { width: 100%; height: auto; display: block; }
|
|
1023
|
+
.el.cartao .cartao-numero {
|
|
1024
|
+
font-family: var(--f-mono);
|
|
1025
|
+
font-size: 5.6cqw; letter-spacing: .06em; opacity: .95;
|
|
1026
|
+
}
|
|
1027
|
+
.el.cartao .cartao-pe {
|
|
1028
|
+
display: flex; justify-content: space-between; gap: 1em;
|
|
1029
|
+
font-size: 3.1cqw; letter-spacing: .13em; text-transform: uppercase;
|
|
1030
|
+
opacity: .8; font-weight: 600;
|
|
1031
|
+
}
|
|
1032
|
+
/* As medidas de dentro são `cqw` — por cento da LARGURA DO CARTÃO, não da
|
|
1033
|
+
tela. Sem o contexto de contêiner o mesmo cartão sai com o chip do tamanho
|
|
1034
|
+
de uma unha na vitrine e do tamanho de uma caixa na cena cheia. */
|
|
1035
|
+
.el.cartao .cartao-corpo { container-type: inline-size; }
|
|
1036
|
+
|
|
1037
|
+
.el.cartao[data-face="verso"] .cartao-corpo { transform: rotate(1.6deg); }
|
|
1038
|
+
.el.cartao[data-mov="flutuar"] .cartao-corpo { animation: cartao-flutua 6s ease-in-out infinite; }
|
|
1039
|
+
.el.cartao[data-mov="girar"] .cartao-corpo { animation: cartao-gira 9s ease-in-out infinite; }
|
|
1040
|
+
@keyframes cartao-flutua {
|
|
1041
|
+
0%, 100% { transform: rotate(-1.6deg) translateY(0); }
|
|
1042
|
+
50% { transform: rotate(-.6deg) translateY(-2.2%); }
|
|
1043
|
+
}
|
|
1044
|
+
@keyframes cartao-gira {
|
|
1045
|
+
0%, 100% { transform: rotate(-1.6deg) rotateY(-16deg); }
|
|
1046
|
+
50% { transform: rotate(-1.6deg) rotateY(16deg); }
|
|
1047
|
+
}
|
|
1048
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1049
|
+
.el.cartao .cartao-corpo { animation: none; }
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/* ── a tela do app ────────────────────────────────────────────────────────
|
|
1053
|
+
Vale sozinha na cena e vale dentro do `dispositivo`. É o mesmo elemento de
|
|
1054
|
+
propósito: com dois, o print da campanha e a tela do celular divergiriam na
|
|
1055
|
+
primeira mudança. */
|
|
1056
|
+
.el.app {
|
|
1057
|
+
width: 100%;
|
|
1058
|
+
font-size: calc(var(--u, 10px) * 3.4);
|
|
1059
|
+
display: flex; flex-direction: column; gap: 4%;
|
|
1060
|
+
text-align: left;
|
|
1061
|
+
background: #fff; color: #10161d;
|
|
1062
|
+
border-radius: 22px; padding: 7% 6.5%;
|
|
1063
|
+
box-shadow: 0 18px 50px rgba(0, 0, 0, .16);
|
|
1064
|
+
}
|
|
1065
|
+
.el.app[data-escuro] { background: #0e1319; color: #eaf0f6; box-shadow: none; }
|
|
1066
|
+
.el.app .app-topo {
|
|
1067
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1068
|
+
font-size: .62em; font-weight: 700; opacity: .75;
|
|
1069
|
+
}
|
|
1070
|
+
.el.app .app-topo i {
|
|
1071
|
+
width: 2.2em; height: .34em; border-radius: 999px;
|
|
1072
|
+
background: currentColor; opacity: .25; display: block;
|
|
1073
|
+
}
|
|
1074
|
+
.el.app .app-valor { display: flex; flex-direction: column; gap: .12em; }
|
|
1075
|
+
.el.app .app-valor small { font-size: .5em; opacity: .6; font-weight: 600; }
|
|
1076
|
+
.el.app .app-valor strong {
|
|
1077
|
+
font-size: 1.5em; font-weight: 800; letter-spacing: -.02em;
|
|
1078
|
+
font-variant-numeric: tabular-nums;
|
|
1079
|
+
}
|
|
1080
|
+
.el.app .app-linhas { display: flex; flex-direction: column; }
|
|
1081
|
+
/* O FIO entre as linhas é o que faz o olho ler "extrato". Sem ele são frases
|
|
1082
|
+
empilhadas, que é como a tela saía antes. */
|
|
1083
|
+
.el.app .app-linha {
|
|
1084
|
+
display: flex; align-items: center; justify-content: space-between; gap: 1em;
|
|
1085
|
+
padding: .62em 0; font-size: .56em;
|
|
1086
|
+
border-top: 1px solid color-mix(in srgb, currentColor 14%, transparent);
|
|
1087
|
+
}
|
|
1088
|
+
.el.app .app-linha:first-child { border-top: 0; }
|
|
1089
|
+
.el.app .app-linha span { opacity: .82; }
|
|
1090
|
+
.el.app .app-linha b { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
1091
|
+
.el.app .app-botao {
|
|
1092
|
+
margin-top: auto;
|
|
1093
|
+
background: var(--c-tom); color: #fff;
|
|
1094
|
+
border-radius: 999px; padding: .78em 1em;
|
|
1095
|
+
text-align: center; font-weight: 700; font-size: .58em;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/* Dentro do aparelho a tela do app OCUPA o vidro: com o padrão do
|
|
1099
|
+
`dispositivo` (conteúdo centrado com folga) ela ficava boiando no meio, e o
|
|
1100
|
+
que se vê num celular de verdade é a tela cheia. */
|
|
1101
|
+
.el.dispositivo .tela { justify-content: flex-start; }
|
|
1102
|
+
.el.dispositivo .tela > .el.app {
|
|
1103
|
+
flex: 1; border-radius: 0; box-shadow: none;
|
|
1104
|
+
margin: -26px -20px; padding: 34px 22px 26px;
|
|
1105
|
+
background: transparent; color: var(--c-texto);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/* ── vitrine ──────────────────────────────────────────────────────────────
|
|
1109
|
+
A linha de produtos com um escolhido. O item atual vem à frente; os outros
|
|
1110
|
+
recuam — sem isso são quatro cartões do mesmo tamanho e ninguém sabe qual
|
|
1111
|
+
está sendo oferecido. */
|
|
1112
|
+
.el.vitrine {
|
|
1113
|
+
width: 100%; font-size: calc(var(--u, 10px) * 3.4);
|
|
1114
|
+
display: flex; flex-direction: column; gap: 1.2em;
|
|
1115
|
+
}
|
|
1116
|
+
.el.vitrine .vitrine-titulo {
|
|
1117
|
+
text-align: center; font-size: .62em; font-weight: 700;
|
|
1118
|
+
letter-spacing: .04em; opacity: .82;
|
|
1119
|
+
}
|
|
1120
|
+
.el.vitrine .vitrine-fila {
|
|
1121
|
+
display: flex; align-items: center; justify-content: center; gap: 2.2%;
|
|
1122
|
+
}
|
|
1123
|
+
.el.vitrine .vitrine-item {
|
|
1124
|
+
flex: 1 1 0; min-width: 0;
|
|
1125
|
+
display: flex; flex-direction: column; align-items: center; gap: .5em;
|
|
1126
|
+
/* Recuado, não apagado: a .42 sobre fundo escuro os outros cartões somem, e
|
|
1127
|
+
uma vitrine com um produto só não é vitrine. */
|
|
1128
|
+
opacity: .58; transform: scale(.86);
|
|
1129
|
+
transition: opacity .4s ease, transform .4s ease;
|
|
1130
|
+
}
|
|
1131
|
+
.el.vitrine .vitrine-item.atual { opacity: 1; transform: scale(1.06); z-index: 2; }
|
|
1132
|
+
.el.vitrine .vitrine-item small {
|
|
1133
|
+
font-size: .42em; opacity: .75; text-align: center; font-weight: 600;
|
|
1134
|
+
}
|
|
1135
|
+
.el.vitrine .el.cartao { width: 100%; }
|
|
1136
|
+
|
|
1137
|
+
/* ── assinatura ───────────────────────────────────────────────────────────
|
|
1138
|
+
O fim do anúncio: o logo sozinho, grande, sobre a cor da casa. Ela toma a
|
|
1139
|
+
cena INTEIRA porque é isso que uma assinatura é — se dividisse espaço com
|
|
1140
|
+
outra coisa, seria mais um bloco em vez do ponto final. */
|
|
1141
|
+
.el.assinatura {
|
|
1142
|
+
position: absolute; inset: 0; z-index: 4;
|
|
1143
|
+
font-size: calc(var(--u, 10px) * 3);
|
|
1144
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
1145
|
+
gap: .5em; text-align: center; padding: 8%;
|
|
1146
|
+
}
|
|
1147
|
+
.el.assinatura[data-sobre="marca"] { background: var(--c-tom); color: #fff; }
|
|
1148
|
+
.el.assinatura[data-sobre="claro"] { background: #fff; color: #10161d; }
|
|
1149
|
+
.el.assinatura[data-sobre="escuro"] { background: #080b0e; color: #fff; }
|
|
1150
|
+
.el.assinatura .assinatura-logo { width: min(52%, 520px); height: auto; display: block; }
|
|
1151
|
+
.el.assinatura .assinatura-nome {
|
|
1152
|
+
font-size: 3.6em; font-weight: 800; letter-spacing: -.03em; line-height: 1;
|
|
1153
|
+
}
|
|
1154
|
+
.el.assinatura .assinatura-frase { font-size: .62em; opacity: .82; letter-spacing: .02em; }
|
|
1155
|
+
|
|
1156
|
+
/* Título de anúncio quebra EQUILIBRADO. "Um cartão. Seu jeito." saía em
|
|
1157
|
+
quatro linhas — três palavras e um órfão —, e é assim que uma frase curta
|
|
1158
|
+
vira um bloco desengonçado no meio do quadro. */
|
|
1159
|
+
.el.texto.titulo, .el.texto[data-papel="titulo"] { text-wrap: balance; }
|