primocode 8.36.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/package.json +2 -2
- package/studio/ferramentas/corte.md +100 -0
- package/studio/server.py +29 -0
- package/studio/web/js/render.js +243 -1
- package/studio/web/render.css +227 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.37.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js && node test/vocabulario.test.js && node test/video-export.test.js && node test/monotonia.test.js && node test/clipe.test.js && node test/midia-envio.test.js && node test/pausas.test.js && node test/fala-repetida.test.js && node test/trilha.test.js && node test/pipeline.test.js && node test/app.test.js && node test/elementos.test.js && node test/remotion.test.js && node test/aplicativo.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/codex.test.js && node test/chaves.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/janela.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js && node test/vocabulario.test.js && node test/video-export.test.js && node test/monotonia.test.js && node test/clipe.test.js && node test/midia-envio.test.js && node test/pausas.test.js && node test/fala-repetida.test.js && node test/trilha.test.js && node test/anuncio.test.js && node test/pipeline.test.js && node test/app.test.js && node test/elementos.test.js && node test/remotion.test.js && node test/aplicativo.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/codex.test.js && node test/chaves.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/janela.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
@@ -227,6 +227,106 @@ baratos de escrever e mudam completamente o resultado.
|
|
|
227
227
|
último, é o vídeo que o usuário chamou de "sem efeito nenhum" — e não é falta
|
|
228
228
|
de recurso: é este bloco não ter sido lido.
|
|
229
229
|
|
|
230
|
+
## Anúncio de marca — o kit
|
|
231
|
+
|
|
232
|
+
Quando o pedido é o COMERCIAL de um produto (um cartão, um app, um plano), o
|
|
233
|
+
que separa uma peça de agência de um slide com música são quatro coisas, e as
|
|
234
|
+
quatro têm elemento próprio. Uma peça de marca sem elas sai genérica por mais
|
|
235
|
+
bonito que esteja o texto.
|
|
236
|
+
|
|
237
|
+
**Primeiro, declare a marca no documento.** Ela pinta o destaque de tudo —
|
|
238
|
+
número, botão, detalhe, o cartão — e libera o fundo `"marca"`:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
"marca": { "cor": "#21C25E", "logo": "/midia/logo.svg" },
|
|
242
|
+
"tema": "claro"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Tema CLARO na maioria dos anúncios de marca. O escuro é para a cena de
|
|
246
|
+
produto e para a vitrine; o resto da peça respira no branco.
|
|
247
|
+
|
|
248
|
+
### `cartao` — o produto
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{ "tipo": "cartao", "nome": "PicPay", "variante": "Gold", "movimento": "flutuar" }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Sai um cartão desenhado: chip, símbolo de aproximação, numeração mascarada,
|
|
255
|
+
nome e validade, com luz e sombra. `cor` (sem ela, a da marca), `largura`,
|
|
256
|
+
`numero` (só os 4 últimos aparecem), `titular`, `validade`, `face: "verso"`,
|
|
257
|
+
`movimento`: `flutuar` | `girar`.
|
|
258
|
+
|
|
259
|
+
**Mostre o produto antes de falar dele.** Um anúncio de cartão que só tem
|
|
260
|
+
frases é um anúncio sem produto.
|
|
261
|
+
|
|
262
|
+
### `app` — a tela do aplicativo
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{ "tipo": "app", "topo": "Minha conta", "rotulo": "saldo disponível",
|
|
266
|
+
"valor": "R$ 1.284,90",
|
|
267
|
+
"linhas": [["Café da manhã", "R$ 18,90"], ["Mercado", "R$ 92,40"]],
|
|
268
|
+
"botao": "Pagar com o cartão" }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Cabeçalho, um número grande, linhas com o nome à esquerda e o valor à direita
|
|
272
|
+
separadas por fio, e o botão cheio embaixo — que é o que faz o olho ler
|
|
273
|
+
"aplicativo". Aceita `["nome", "valor"]` e `"nome|valor"`. `escuro: true` para
|
|
274
|
+
a versão noturna.
|
|
275
|
+
|
|
276
|
+
**Ele vai DENTRO do `dispositivo`**, e aí ocupa a tela inteira do aparelho:
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{ "tipo": "dispositivo", "forma": "celular", "dentro": [ { "tipo": "app", ... } ] }
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Solto na cena ele vira o print da campanha. É o mesmo elemento de propósito:
|
|
283
|
+
com dois, a tela do celular e o print divergiriam na primeira mudança.
|
|
284
|
+
|
|
285
|
+
### `vitrine` — a linha de produtos
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{ "tipo": "vitrine", "titulo": "Escolha seu cartão", "atual": 1, "nome": "PicPay",
|
|
289
|
+
"itens": [ { "nome": "Gold", "cor": "#C9A227", "detalhe": "sem anuidade" },
|
|
290
|
+
{ "nome": "Verde", "detalhe": "cashback" },
|
|
291
|
+
{ "nome": "Black", "cor": "#181B1F", "detalhe": "sala VIP" } ] }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Os cartões numa fila, o `atual` à frente e os outros recuados. Vai bem sobre
|
|
295
|
+
fundo escuro (`"fundo": "#0b0f0c"`), que é como toda tela de "escolha o seu" é
|
|
296
|
+
desenhada.
|
|
297
|
+
|
|
298
|
+
### `assinatura` — o ponto final
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{ "tipo": "assinatura", "nome": "PicPay", "frase": "o jeito fácil de pagar", "sobre": "marca" }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Ela toma a CENA INTEIRA: o logo sozinho, grande, sobre a cor da casa. `sobre`:
|
|
305
|
+
`marca` | `claro` | `escuro`. Com `logo` ela usa a imagem; sem, o `nome`.
|
|
306
|
+
|
|
307
|
+
**Todo anúncio termina assim.** Sem a assinatura a peça acaba numa frase, e
|
|
308
|
+
ninguém fica sabendo quem falou.
|
|
309
|
+
|
|
310
|
+
### A ordem que funciona
|
|
311
|
+
|
|
312
|
+
Uma peça de marca de 20 a 30 segundos, com 8 a 10 cenas:
|
|
313
|
+
|
|
314
|
+
| # | cena | o que entra |
|
|
315
|
+
|---|---|---|
|
|
316
|
+
| 1 | a promessa, em uma frase curta | `texto` pequeno, muito ar, fundo claro |
|
|
317
|
+
| 2 | o produto | `cartao` com `movimento` |
|
|
318
|
+
| 3 | o mote, na cor da casa | `fundo: "marca"`, `texto` branco, `anim: "palavra"` |
|
|
319
|
+
| 4 | funcionando | `dispositivo` + `app` |
|
|
320
|
+
| 5 | o benefício | `texto` |
|
|
321
|
+
| 6 | a linha | `vitrine` sobre fundo escuro |
|
|
322
|
+
| 7 | a última razão | `texto` |
|
|
323
|
+
| 8 | assinatura | `assinatura` |
|
|
324
|
+
|
|
325
|
+
E o rosto de quem fala, quando houver, entra por `clipe` entre a 4 e a 5 — o
|
|
326
|
+
depoimento é o que dá confiança, e nenhum desenho substitui.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
230
330
|
### `icone` — o desenho ao lado do argumento
|
|
231
331
|
|
|
232
332
|
```json
|
package/studio/server.py
CHANGED
|
@@ -893,6 +893,28 @@ def revisar_monotonia(doc, ferramenta):
|
|
|
893
893
|
"(aproximar, afastar, esquerda, direita, cima), e uma ou duas peças por "
|
|
894
894
|
"vídeo levam chaves de animação.")
|
|
895
895
|
|
|
896
|
+
# ── ANÚNCIO DE MARCA SEM PRODUTO E SEM ASSINATURA ───────────────────
|
|
897
|
+
#
|
|
898
|
+
# "Ainda não tá bom, tá genérico. Quero nesse nível." — o dono, sobre um
|
|
899
|
+
# comercial de fintech mandado como referência.
|
|
900
|
+
#
|
|
901
|
+
# O que separava a peça daqui daquela não era tema nem cor: era não ter o
|
|
902
|
+
# PRODUTO na tela e não assinar no fim. Quem declarou uma `marca` está
|
|
903
|
+
# fazendo peça de marca, e aí estas duas ausências são o defeito inteiro.
|
|
904
|
+
if doc.get("marca") and len(cenas) >= 4:
|
|
905
|
+
if not (tipos & {"cartao", "app", "vitrine", "dispositivo", "imagem", "clipe"}):
|
|
906
|
+
avisos.append(
|
|
907
|
+
"esta peça tem marca declarada e nenhuma cena mostra o PRODUTO — são %d cenas "
|
|
908
|
+
"de texto sobre fundo. Um anúncio de cartão mostra o cartão (`cartao`), o app "
|
|
909
|
+
"funcionando dentro do celular (`dispositivo` + `app`) e a linha de produtos "
|
|
910
|
+
"(`vitrine`). É essa ausência que se lê como genérico." % len(cenas))
|
|
911
|
+
if "assinatura" not in tipos:
|
|
912
|
+
avisos.append(
|
|
913
|
+
'o anúncio acaba sem assinar. A última cena de uma peça de marca é '
|
|
914
|
+
'{"tipo":"assinatura","nome":"…","sobre":"marca"} — o logo sozinho, grande, '
|
|
915
|
+
"sobre a cor da casa. Sem ela a peça termina numa frase e ninguém fica "
|
|
916
|
+
"sabendo quem falou.")
|
|
917
|
+
|
|
896
918
|
# Só a voz com tela montada: existe e quase nunca é lembrado, porque é o
|
|
897
919
|
# contrário do `mudo` e o contrário não estava no vocabulário até agora.
|
|
898
920
|
clipes = [e for e in todos if e.get("tipo") == "clipe"]
|
|
@@ -1061,9 +1083,16 @@ def validar_doc(modelo, doc):
|
|
|
1061
1083
|
# default do renderizador: texto cru de 16px num canvas de 1920 — ou nada,
|
|
1062
1084
|
# quando o texto estava noutro campo. Era o "vídeo com música mas tudo
|
|
1063
1085
|
# preto": documento válido na forma, invisível na tela.
|
|
1086
|
+
# O kit de anúncio (cartao, app, vitrine, assinatura) entra na lista abaixo.
|
|
1087
|
+
#
|
|
1088
|
+
# A LISTA É LIDA POR UM TESTE (`vocabulario.test.js`), que a recorta com uma
|
|
1089
|
+
# expressão regular e pega as palavras — então comentário DENTRO dela vira
|
|
1090
|
+
# "tipo que o render precisa desenhar", e o teste passa a cobrar `produto`,
|
|
1091
|
+
# `linha` e `fim`. Texto sobre os tipos mora aqui fora.
|
|
1064
1092
|
TIPOS_VALIDOS = {"texto", "lista", "cartoes", "numeros", "tabela", "codigo",
|
|
1065
1093
|
"citacao", "forma", "imagem", "icone", "grafico", "no", "conector",
|
|
1066
1094
|
"objeto3d", "clipe",
|
|
1095
|
+
"cartao", "app", "vitrine", "assinatura",
|
|
1067
1096
|
"selo", "progresso", "avatar", "dispositivo", "conversa",
|
|
1068
1097
|
"passos", "opcoes"}
|
|
1069
1098
|
# Os sete últimos são o vocabulário do vídeo de referência. O comentário
|
package/studio/web/js/render.js
CHANGED
|
@@ -115,6 +115,13 @@ function aplicarFundo(no, fundo) {
|
|
|
115
115
|
// Era metade do "os vídeos ficaram todos iguais".
|
|
116
116
|
if (/^(malha|brilho|grade|pontos|suave|vinheta|aurora|nevoa|ondas|raios|xadrez)$/.test(fundo)) {
|
|
117
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");
|
|
118
125
|
} else no.style.background = fundo;
|
|
119
126
|
return;
|
|
120
127
|
}
|
|
@@ -214,11 +221,30 @@ function svgGrafico(e, l, a) {
|
|
|
214
221
|
|
|
215
222
|
/* -------------------------------------------------------------- elementos */
|
|
216
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. */
|
|
217
242
|
var TIPOS_CONHECIDOS = { texto: 1, lista: 1, cartoes: 1, numeros: 1, tabela: 1, codigo: 1,
|
|
218
243
|
citacao: 1, forma: 1, imagem: 1, icone: 1, grafico: 1, no: 1, conector: 1,
|
|
219
244
|
objeto3d: 1, clipe: 1,
|
|
220
245
|
selo: 1, progresso: 1, avatar: 1, dispositivo: 1, conversa: 1, passos: 1,
|
|
221
|
-
opcoes: 1
|
|
246
|
+
opcoes: 1,
|
|
247
|
+
cartao: 1, app: 1, vitrine: 1, assinatura: 1 };
|
|
222
248
|
/* Os sete últimos são o vocabulário do vídeo de referência: pílula de estado,
|
|
223
249
|
trilha de progresso, foto de pessoa, celular/notebook com conteúdo dentro,
|
|
224
250
|
balões de conversa, etapas com uma acesa, e pergunta com alternativas.
|
|
@@ -955,6 +981,202 @@ function renderElemento(e, ctx) {
|
|
|
955
981
|
break;
|
|
956
982
|
}
|
|
957
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
|
+
|
|
958
1180
|
case "icone": {
|
|
959
1181
|
var t = e.tamanho || 90;
|
|
960
1182
|
no.style.width = _px(e.l || t);
|
|
@@ -1188,6 +1410,11 @@ function renderCena(cena, doc, op) {
|
|
|
1188
1410
|
elementos = elementos.filter(function (e) {
|
|
1189
1411
|
if (!e || e.x != null || e.y != null) return true;
|
|
1190
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; }
|
|
1191
1418
|
// Legenda de fala mora no terço de baixo, como toda legenda de vídeo.
|
|
1192
1419
|
// Pelo fluxo ela iria para o meio da tela, em cima do rosto de quem fala.
|
|
1193
1420
|
if (e.tipo === "texto" && e.papel === "fala") { falas.push(e); return false; }
|
|
@@ -1203,6 +1430,21 @@ function renderCena(cena, doc, op) {
|
|
|
1203
1430
|
// tela vertical pede tipografia maior: os padrões são calibrados em 1920x1080
|
|
1204
1431
|
var escalaTipo = H > W ? 1.35 : 1;
|
|
1205
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");
|
|
1206
1448
|
var ctx = {
|
|
1207
1449
|
i: 0, escalaTipo: escalaTipo,
|
|
1208
1450
|
animPadrao: op.animar ? (cena.anim || "subir") : null,
|
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; }
|