primocode 8.26.0 → 8.27.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/00-protocolo.md +2 -1
- package/studio/ferramentas/01-modelos.md +3 -2
- package/studio/ferramentas/corte.md +81 -0
- package/studio/server.py +9 -3
- package/studio/web/js/ferramentas.js +2 -2
- package/studio/web/js/render.js +219 -5
- package/studio/web/render.css +277 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.27.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. 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/app.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/app.test.js && node test/elementos.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
@@ -57,7 +57,8 @@ Em dúvida entre duas, escolha a que o usuário vai **mostrar para outra pessoa*
|
|
|
57
57
|
- **Ritmo.** Em decks longos, um `layout: "secao"` a cada 3–5 cenas.
|
|
58
58
|
- **Escolha o tema pelo assunto:** `meia-noite` produto e tecnologia · `claro`
|
|
59
59
|
corporativo e projetor · `papel` editorial e longo · `neon` técnico e dev ·
|
|
60
|
-
`brasa` energia e urgência · `floresta` sustentabilidade e saúde
|
|
60
|
+
`brasa` energia e urgência · `floresta` sustentabilidade e saúde ·
|
|
61
|
+
`ambar` curso, aula e conteúdo longo · `grafite` técnico sóbrio e relatório.
|
|
61
62
|
- **Notas do apresentador** (`notas` na cena) em quem vai falar por cima.
|
|
62
63
|
- **Nunca invente dado com cara de real.** Sem fonte, escreva a ordem de grandeza
|
|
63
64
|
ou deixe o campo como pergunta explícita ao usuário.
|
|
@@ -134,9 +134,10 @@ Sem declarar nada, os elementos já entram em cascata na ordem em que aparecem.
|
|
|
134
134
|
|
|
135
135
|
### Temas
|
|
136
136
|
|
|
137
|
-
`meia-noite` `claro` `papel` `neon` `brasa` `floresta` `retro` `oceano` `doce`
|
|
137
|
+
`meia-noite` `claro` `papel` `neon` `brasa` `floresta` `retro` `oceano` `doce`
|
|
138
|
+
`mono` `ambar` `grafite`
|
|
138
139
|
|
|
139
|
-
São esses
|
|
140
|
+
São esses doze, e só. Nome fora da lista é recusado: escuro é `meia-noite`, não
|
|
140
141
|
`noite`. Vale no documento ou por cena.
|
|
141
142
|
|
|
142
143
|
### Identidade — o que separa um vídeo genérico de um da marca
|
|
@@ -89,6 +89,8 @@ cena. Se o assunto não empurrar para nenhuma, escolha a que você NÃO usou da
|
|
|
89
89
|
| explicação, tutorial, passo a passo | `papel` ou `claro` | `lofi` | **Aula** |
|
|
90
90
|
| conquista, agradecimento, celebração | `doce` ou `neon` | `alegre` | **Brinde** |
|
|
91
91
|
| memória, história, retrospectiva | `retro` ou `mono` | `lofi` | **Linha** |
|
|
92
|
+
| curso, aula, treinamento, método | `ambar` | `lofi` | **Aula** |
|
|
93
|
+
| relatório, número, análise técnica | `grafite` | `ambiente` | **Manchete** |
|
|
92
94
|
|
|
93
95
|
### As seis estruturas
|
|
94
96
|
|
|
@@ -178,6 +180,85 @@ livre de direitos, ou uma das trilhas geradas acima.
|
|
|
178
180
|
|
|
179
181
|
---
|
|
180
182
|
|
|
183
|
+
## Elementos que dão cara de produção
|
|
184
|
+
|
|
185
|
+
Estes seis existem porque um vídeo genérico é feito só de título + texto. São
|
|
186
|
+
baratos de escrever e mudam completamente o resultado.
|
|
187
|
+
|
|
188
|
+
### `selo` — a pílula de estado
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{ "tipo": "selo", "texto": "Módulo 2 de 5" }
|
|
192
|
+
{ "tipo": "selo", "texto": "Resultado 4", "icone": "★", "estilo": "vivo" }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`estilo`: sem nada (contorno), `"vivo"` (preenchido) ou `"solto"` (só o texto
|
|
196
|
+
em versalete). Vai sozinho para cima do título.
|
|
197
|
+
|
|
198
|
+
### `progresso` — onde estamos no caminho
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{ "tipo": "progresso", "atual": 4, "total": 15 }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Mostra "4/15" e os pontinhos. Use em série de cenas: diz ao espectador que
|
|
205
|
+
existe um percurso, e isso segura ele até o fim. `"numero": false` deixa só
|
|
206
|
+
os pontos.
|
|
207
|
+
|
|
208
|
+
### `avatar` — a pessoa
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{ "tipo": "avatar", "src": "/midia/foto.jpg", "nome": "Cleiton Paris", "tamanho": 280 }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Círculo com anel aceso. Sem `src`, mostra as iniciais do `nome` — melhor que
|
|
215
|
+
um buraco cinza. Vai para cima do nome sozinho.
|
|
216
|
+
|
|
217
|
+
### `dispositivo` — celular, notebook ou navegador com conteúdo dentro
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "tipo": "dispositivo", "forma": "celular", "dentro": [ ...elementos... ] }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`forma`: `celular` | `notebook` | `tablet` | `navegador` (este aceita
|
|
224
|
+
`"endereco": "primocode.dev"`). `dentro` é uma lista de elementos comuns — o
|
|
225
|
+
que couber. Nos layouts `imagem-direita` / `imagem-esquerda` o aparelho vai
|
|
226
|
+
para o lado sozinho, com o texto do outro. É o enquadramento mais forte que
|
|
227
|
+
existe aqui: use quando estiver mostrando uma tela.
|
|
228
|
+
|
|
229
|
+
### `conversa` — balões de pergunta e resposta
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{ "tipo": "conversa", "itens": [
|
|
233
|
+
{ "de": "eu", "texto": "Como acelero meu workflow?" },
|
|
234
|
+
{ "de": "ela", "texto": "Peça o storyboard em seis quadros." } ] }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Entram um de cada vez, como diálogo. `de: "eu"` à esquerda; qualquer outra
|
|
238
|
+
coisa à direita, com a cor do tema. Combina com `dispositivo` — a conversa
|
|
239
|
+
dentro do celular é a cena mais usada em vídeo de IA.
|
|
240
|
+
|
|
241
|
+
### `passos` e `opcoes`
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{ "tipo": "passos", "atual": 2, "itens": ["Passo 1|roteiro", "Passo 2|storyboard", "Passo 3|corte"] }
|
|
245
|
+
{ "tipo": "opcoes", "pergunta": "O que garante o look?", "certa": 1,
|
|
246
|
+
"itens": ["LUT uniforme", "Saturação aleatória", "Espaços de cor diferentes"] }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`passos` acende a etapa `atual` e apaga as outras — mude o `atual` a cada
|
|
250
|
+
cena e a série ganha movimento sem esforço. `opcoes` marca a alternativa
|
|
251
|
+
`certa` com ✓ (não escreva o ✓ no texto: ele vem sozinho). Alternativa faz o
|
|
252
|
+
espectador pensar antes da resposta, e pensar é o que segura alguém no vídeo.
|
|
253
|
+
|
|
254
|
+
### Vidro
|
|
255
|
+
|
|
256
|
+
`"estilo": "vidro"` em `cartoes` dá painel translúcido com bisel de luz em
|
|
257
|
+
cima e sombra embaixo — o "liquid glass". Funciona em tema escuro; em tema
|
|
258
|
+
claro use os cartões normais.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
181
262
|
## Editar o vídeo do usuário (`clipe`)
|
|
182
263
|
|
|
183
264
|
Quando a pessoa manda uma gravação dela — o rosto falando, uma tela gravada,
|
package/studio/server.py
CHANGED
|
@@ -784,7 +784,7 @@ def gravar_analise(ident, dados):
|
|
|
784
784
|
# "Ele não alterna entre os temas, usa um tema só pra todos os vídeos, e o
|
|
785
785
|
# vídeo sempre tem a mesma estrutura. Fica tudo genérico."
|
|
786
786
|
#
|
|
787
|
-
# O estúdio tem
|
|
787
|
+
# O estúdio tem doze temas, onze fundos, oito transições e seis trilhas. O
|
|
788
788
|
# agente usa uma fatia estreita e repete — porque a spec tem UM exemplo
|
|
789
789
|
# completo e modelo ancora em exemplo. Conselho escrito na spec ajuda pouco
|
|
790
790
|
# quando ela nem sempre é relida.
|
|
@@ -922,7 +922,7 @@ def _texto_do_doc(doc):
|
|
|
922
922
|
# Os temas que o render.css de fato define. Fora desta lista não existe cor
|
|
923
923
|
# nenhuma — foi o que aconteceu com "noite", que passou como se fosse tema.
|
|
924
924
|
TEMAS_VALIDOS = {"claro", "meia-noite", "papel", "neon", "brasa", "floresta",
|
|
925
|
-
"retro", "oceano", "doce", "mono"}
|
|
925
|
+
"retro", "oceano", "doce", "mono", "ambar", "grafite"}
|
|
926
926
|
|
|
927
927
|
|
|
928
928
|
def _tema_invalido(tema, onde=""):
|
|
@@ -989,7 +989,13 @@ def validar_doc(modelo, doc):
|
|
|
989
989
|
# preto": documento válido na forma, invisível na tela.
|
|
990
990
|
TIPOS_VALIDOS = {"texto", "lista", "cartoes", "numeros", "tabela", "codigo",
|
|
991
991
|
"citacao", "forma", "imagem", "icone", "grafico", "no", "conector",
|
|
992
|
-
"objeto3d", "clipe"
|
|
992
|
+
"objeto3d", "clipe",
|
|
993
|
+
"selo", "progresso", "avatar", "dispositivo", "conversa",
|
|
994
|
+
"passos", "opcoes"}
|
|
995
|
+
# Os sete últimos são o vocabulário do vídeo de referência. O comentário
|
|
996
|
+
# mora aqui fora porque test/vocabulario.test.js lê esta lista com
|
|
997
|
+
# expressão regular, comparando com a do render.js — palavra de comentário
|
|
998
|
+
# dentro das chaves entrava na lista como se fosse um tipo válido.
|
|
993
999
|
|
|
994
1000
|
# Tema inventado tinha o mesmo destino silencioso do tipo inventado: o CSS
|
|
995
1001
|
# não casa com [data-tema="noite"], nenhuma cor é definida e a peça sai
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
ferramentas.js — o que cada ferramenta oferece na interface.
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
|
-
// Os
|
|
5
|
+
// Os doze que o render.css define. A lista tinha seis: quatro temas existiam
|
|
6
6
|
// no CSS, a IA podia usá-los, e o editor não os oferecia a ninguém.
|
|
7
7
|
var TEMAS = ["meia-noite", "claro", "papel", "neon", "brasa", "floresta",
|
|
8
|
-
"retro", "oceano", "doce", "mono"];
|
|
8
|
+
"retro", "oceano", "doce", "mono", "ambar", "grafite"];
|
|
9
9
|
|
|
10
10
|
var PALETA = ["#ff5a36", "#ffb03a", "#f4e04d", "#2fbf7f", "#1f8fe0", "#8b5cf6",
|
|
11
11
|
"#f472b6", "#ffffff", "#9aa4b2", "#111318"];
|
package/studio/web/js/render.js
CHANGED
|
@@ -162,7 +162,16 @@ function svgGrafico(e, l, a) {
|
|
|
162
162
|
|
|
163
163
|
var TIPOS_CONHECIDOS = { texto: 1, lista: 1, cartoes: 1, numeros: 1, tabela: 1, codigo: 1,
|
|
164
164
|
citacao: 1, forma: 1, imagem: 1, icone: 1, grafico: 1, no: 1, conector: 1,
|
|
165
|
-
objeto3d: 1, clipe: 1
|
|
165
|
+
objeto3d: 1, clipe: 1,
|
|
166
|
+
selo: 1, progresso: 1, avatar: 1, dispositivo: 1, conversa: 1, passos: 1,
|
|
167
|
+
opcoes: 1 };
|
|
168
|
+
/* Os sete últimos são o vocabulário do vídeo de referência: pílula de estado,
|
|
169
|
+
trilha de progresso, foto de pessoa, celular/notebook com conteúdo dentro,
|
|
170
|
+
balões de conversa, etapas com uma acesa, e pergunta com alternativas.
|
|
171
|
+
|
|
172
|
+
O comentário mora AQUI FORA porque test/vocabulario.test.js lê esta lista
|
|
173
|
+
com expressão regular, comparando com a do servidor — e palavra de
|
|
174
|
+
comentário dentro das chaves entrava na lista como se fosse um tipo. */
|
|
166
175
|
|
|
167
176
|
/* ── chaves de animação ──────────────────────────────────────────────────
|
|
168
177
|
Transforma a lista de instantes de um elemento numa regra @keyframes de
|
|
@@ -528,6 +537,192 @@ function renderElemento(e, ctx) {
|
|
|
528
537
|
break;
|
|
529
538
|
}
|
|
530
539
|
|
|
540
|
+
/* ── selo ────────────────────────────────────────────────────────────
|
|
541
|
+
A pílula pequena, em versalete, que diz onde a cena está: "AVANÇADO",
|
|
542
|
+
"MÓDULO 2 DE 5", "RESULTADO 4". Custa quase nada e é o que separa uma
|
|
543
|
+
cena que parece produzida de uma cena que parece um slide.
|
|
544
|
+
|
|
545
|
+
Antes disso a saída era um `texto` com papel "chapeu", que é só letra
|
|
546
|
+
menor — sem a pílula, sem a marcação de estado. */
|
|
547
|
+
case "selo": {
|
|
548
|
+
no.textContent = "";
|
|
549
|
+
if (e.icone) {
|
|
550
|
+
var ic = document.createElement("span");
|
|
551
|
+
ic.className = "selo-icone";
|
|
552
|
+
ic.textContent = e.icone;
|
|
553
|
+
no.appendChild(ic);
|
|
554
|
+
}
|
|
555
|
+
var rot = document.createElement("span");
|
|
556
|
+
rot.textContent = e.texto || e.rotulo || "";
|
|
557
|
+
no.appendChild(rot);
|
|
558
|
+
if (e.estilo) no.setAttribute("data-estilo", e.estilo); // "vivo" | "solto"
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* ── progresso ───────────────────────────────────────────────────────
|
|
563
|
+
"4 de 15", com os pontinhos. Diz ao espectador que existe um caminho e
|
|
564
|
+
onde ele está nele — é o que faz um vídeo de dez cenas não parecer dez
|
|
565
|
+
cenas soltas. */
|
|
566
|
+
case "progresso": {
|
|
567
|
+
var total = Math.max(1, Math.min(40, Number(e.total) || 1));
|
|
568
|
+
var atual = Math.max(1, Math.min(total, Number(e.atual) || 1));
|
|
569
|
+
if (e.numero !== false) {
|
|
570
|
+
var num = document.createElement("b");
|
|
571
|
+
num.textContent = atual + "/" + total;
|
|
572
|
+
no.appendChild(num);
|
|
573
|
+
}
|
|
574
|
+
var pontos = document.createElement("span");
|
|
575
|
+
pontos.className = "pontos";
|
|
576
|
+
for (var pi = 1; pi <= total; pi++) {
|
|
577
|
+
var pt = document.createElement("i");
|
|
578
|
+
if (pi === atual) pt.className = "aqui";
|
|
579
|
+
else if (pi < atual) pt.className = "feito";
|
|
580
|
+
pontos.appendChild(pt);
|
|
581
|
+
}
|
|
582
|
+
no.appendChild(pontos);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* ── avatar ──────────────────────────────────────────────────────────
|
|
587
|
+
A foto redonda com o anel aceso. É como se apresenta uma PESSOA — e
|
|
588
|
+
vídeo com rosto retém mais que vídeo sem. Antes só existia `imagem`,
|
|
589
|
+
que é retângulo. */
|
|
590
|
+
case "avatar": {
|
|
591
|
+
var d = e.tamanho || 260;
|
|
592
|
+
no.style.width = _px(d);
|
|
593
|
+
no.style.height = _px(d);
|
|
594
|
+
if (e.src) {
|
|
595
|
+
var foto = document.createElement("img");
|
|
596
|
+
foto.src = e.src;
|
|
597
|
+
foto.alt = e.nome || "";
|
|
598
|
+
no.appendChild(foto);
|
|
599
|
+
} else {
|
|
600
|
+
// Sem foto, as iniciais — melhor que um buraco cinza.
|
|
601
|
+
var ini = document.createElement("b");
|
|
602
|
+
// Em px, calculado do diâmetro. Em `%` o navegador mede contra a
|
|
603
|
+
// fonte do PAI, não contra a caixa: as iniciais saíam com 8px dentro
|
|
604
|
+
// de um círculo de 280 — um pontinho no meio do nada.
|
|
605
|
+
ini.style.fontSize = _px(Math.round(d * 0.34));
|
|
606
|
+
ini.textContent = String(e.nome || "?").trim().split(/\s+/)
|
|
607
|
+
.slice(0, 2).map(function (w) { return w[0]; }).join("").toUpperCase();
|
|
608
|
+
no.appendChild(ini);
|
|
609
|
+
}
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/* ── dispositivo ─────────────────────────────────────────────────────
|
|
614
|
+
O celular, o notebook ou a janela de navegador com conteúdo DENTRO.
|
|
615
|
+
É o enquadramento que faz uma tela parecer uma tela em vez de um
|
|
616
|
+
retângulo de texto — e o vídeo de referência usa isso o tempo todo,
|
|
617
|
+
com a conversa acontecendo dentro do celular.
|
|
618
|
+
|
|
619
|
+
O conteúdo é `dentro`: uma lista de elementos comuns, renderizados
|
|
620
|
+
recursivamente. Assim qualquer coisa cabe ali — conversa, lista,
|
|
621
|
+
imagem — sem inventar um vocabulário só para o miolo. */
|
|
622
|
+
case "dispositivo": {
|
|
623
|
+
var forma = /^(celular|notebook|navegador|tablet)$/.test(e.forma || "")
|
|
624
|
+
? e.forma : "celular";
|
|
625
|
+
no.setAttribute("data-forma", forma);
|
|
626
|
+
if (e.altura) no.style.height = _px(e.altura);
|
|
627
|
+
if (e.largura) no.style.width = _px(e.largura);
|
|
628
|
+
|
|
629
|
+
var moldura = document.createElement("div");
|
|
630
|
+
moldura.className = "moldura";
|
|
631
|
+
|
|
632
|
+
if (forma === "navegador") {
|
|
633
|
+
var barra = document.createElement("div");
|
|
634
|
+
barra.className = "barra-nav";
|
|
635
|
+
barra.innerHTML = "<i></i><i></i><i></i>"
|
|
636
|
+
+ (e.endereco ? '<span class="url">' + _fugir(e.endereco) + "</span>" : "");
|
|
637
|
+
moldura.appendChild(barra);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
var tela = document.createElement("div");
|
|
641
|
+
tela.className = "tela";
|
|
642
|
+
if (e.imagem) {
|
|
643
|
+
tela.style.backgroundImage = "url('" + e.imagem + "')";
|
|
644
|
+
tela.style.backgroundSize = "cover";
|
|
645
|
+
tela.style.backgroundPosition = "center";
|
|
646
|
+
}
|
|
647
|
+
(e.dentro || []).forEach(function (filho) {
|
|
648
|
+
if (!filho || !TIPOS_CONHECIDOS[filho.tipo]) return;
|
|
649
|
+
ctx.i++;
|
|
650
|
+
tela.appendChild(renderElemento(filho, ctx));
|
|
651
|
+
});
|
|
652
|
+
moldura.appendChild(tela);
|
|
653
|
+
no.appendChild(moldura);
|
|
654
|
+
|
|
655
|
+
if (forma === "notebook") {
|
|
656
|
+
var base = document.createElement("div");
|
|
657
|
+
base.className = "base";
|
|
658
|
+
no.appendChild(base);
|
|
659
|
+
}
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* ── conversa ────────────────────────────────────────────────────────
|
|
664
|
+
Balões de pergunta e resposta. É a forma mais direta de mostrar como
|
|
665
|
+
se usa uma ferramenta de IA — e é o que o vídeo de referência faz
|
|
666
|
+
dentro do celular. `de: "eu"` alinha à direita; qualquer outra coisa
|
|
667
|
+
vai para a esquerda, com destaque. */
|
|
668
|
+
case "conversa": {
|
|
669
|
+
(e.itens || []).forEach(function (fala, iFala) {
|
|
670
|
+
var b = document.createElement("div");
|
|
671
|
+
var quem = typeof fala === "string" ? (iFala % 2 ? "ela" : "eu") : (fala.de || "eu");
|
|
672
|
+
b.className = "balao " + (quem === "eu" ? "meu" : "dela");
|
|
673
|
+
b.style.setProperty("--atraso", (0.12 + iFala * 0.28).toFixed(2));
|
|
674
|
+
b.textContent = typeof fala === "string" ? fala : (fala.texto || "");
|
|
675
|
+
no.appendChild(b);
|
|
676
|
+
});
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* ── passos ──────────────────────────────────────────────────────────
|
|
681
|
+
Os cartões de etapa com UM aceso. Diferente de `cartoes`: aqui existe
|
|
682
|
+
ordem e existe "onde estamos agora", e é isso que se quer mostrar. */
|
|
683
|
+
case "passos": {
|
|
684
|
+
var itens = e.itens || [];
|
|
685
|
+
var vivo = Number(e.atual) || 0; // 1-based; 0 = nenhum
|
|
686
|
+
no.style.gridTemplateColumns = "repeat(" + Math.max(1, itens.length) + ", 1fr)";
|
|
687
|
+
itens.forEach(function (it, k) {
|
|
688
|
+
var p = document.createElement("div");
|
|
689
|
+
p.className = "passo" + (k + 1 === vivo ? " agora" : (k + 1 < vivo ? " feito" : ""));
|
|
690
|
+
var partes = typeof it === "string" ? it.split("|") : [it.titulo, it.texto];
|
|
691
|
+
var cab = document.createElement("b");
|
|
692
|
+
cab.textContent = (partes[0] || "").trim();
|
|
693
|
+
p.appendChild(cab);
|
|
694
|
+
if (partes[1]) {
|
|
695
|
+
var sub = document.createElement("span");
|
|
696
|
+
sub.textContent = String(partes[1]).trim();
|
|
697
|
+
p.appendChild(sub);
|
|
698
|
+
}
|
|
699
|
+
no.appendChild(p);
|
|
700
|
+
});
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/* ── opcoes ──────────────────────────────────────────────────────────
|
|
705
|
+
A pergunta com alternativas e uma certa. Faz o espectador PENSAR antes
|
|
706
|
+
de a resposta aparecer — e uma pausa de pensamento é o que segura
|
|
707
|
+
alguém num vídeo. `certa` é o índice (1-based) da alternativa boa. */
|
|
708
|
+
case "opcoes": {
|
|
709
|
+
if (e.pergunta) {
|
|
710
|
+
var q = document.createElement("b");
|
|
711
|
+
q.className = "pergunta";
|
|
712
|
+
q.textContent = e.pergunta;
|
|
713
|
+
no.appendChild(q);
|
|
714
|
+
}
|
|
715
|
+
var certa = Number(e.certa) || 0;
|
|
716
|
+
(e.itens || []).forEach(function (it, k) {
|
|
717
|
+
var o = document.createElement("div");
|
|
718
|
+
o.className = "opcao" + (k + 1 === certa ? " certa" : "");
|
|
719
|
+
o.textContent = typeof it === "string" ? it : (it.texto || "");
|
|
720
|
+
o.style.setProperty("--atraso", (0.1 + k * 0.18).toFixed(2));
|
|
721
|
+
no.appendChild(o);
|
|
722
|
+
});
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
|
|
531
726
|
case "numeros": {
|
|
532
727
|
var m = (e.itens || []).length;
|
|
533
728
|
no.style.gridTemplateColumns = "repeat(" + Math.min(m || 1, e.colunas || m) + ", 1fr)";
|
|
@@ -975,9 +1170,21 @@ function renderCena(cena, doc, op) {
|
|
|
975
1170
|
elementos.forEach(function (e) {
|
|
976
1171
|
if (e.tipo === "conector") return;
|
|
977
1172
|
if (e.x != null || e.y != null) { flutuantes.push(e); return; }
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1173
|
+
/* O aparelho também vai para o lado, não só a imagem. É o enquadramento
|
|
1174
|
+
do vídeo de referência: texto de um lado, celular com a conversa do
|
|
1175
|
+
outro. Sem isto o celular caía na coluna de texto e o outro lado ficava
|
|
1176
|
+
vazio — metade do quadro em branco. */
|
|
1177
|
+
if ((layout === "imagem-direita" || layout === "imagem-esquerda")
|
|
1178
|
+
&& (e.tipo === "imagem" || e.tipo === "dispositivo") && !imagem) { imagem = e; return; }
|
|
1179
|
+
/* Selo, progresso e foto são material de CABEÇALHO: vêm antes do título,
|
|
1180
|
+
como sobrancelha da cena. Caindo no corpo, apareciam DEPOIS do título —
|
|
1181
|
+
a pílula "Módulo 2 de 5" embaixo do nome do módulo, e o rosto da pessoa
|
|
1182
|
+
embaixo do nome dela. Dentro do grupo a ordem é a do documento, então
|
|
1183
|
+
quem escreveu decide o resto. */
|
|
1184
|
+
if (e.papel === "chapeu" || e.papel === "titulo"
|
|
1185
|
+
|| e.tipo === "selo" || e.tipo === "progresso" || e.tipo === "avatar") {
|
|
1186
|
+
cabecalho.push(e);
|
|
1187
|
+
} else corpo.push(e);
|
|
981
1188
|
});
|
|
982
1189
|
|
|
983
1190
|
function solta(lista, alvo) {
|
|
@@ -992,7 +1199,14 @@ function renderCena(cena, doc, op) {
|
|
|
992
1199
|
solta(cabecalho, lado); solta(corpo, lado);
|
|
993
1200
|
var box = document.createElement("div");
|
|
994
1201
|
box.className = "lado-imagem";
|
|
995
|
-
if (imagem) {
|
|
1202
|
+
if (imagem && imagem.tipo === "dispositivo") {
|
|
1203
|
+
// O aparelho tem moldura e sombra próprias: encostar nas bordas do
|
|
1204
|
+
// quadro comeria as duas. Ele entra com respiro.
|
|
1205
|
+
box.classList.add("com-aparelho");
|
|
1206
|
+
box.style.padding = margem + "px " + Math.round(W * 0.04) + "px";
|
|
1207
|
+
ctx.i++;
|
|
1208
|
+
box.appendChild(renderElemento(imagem, ctx));
|
|
1209
|
+
} else if (imagem) {
|
|
996
1210
|
var im = renderElemento({ tipo: "imagem", src: imagem.src, ajuste: imagem.ajuste, raio: 0 }, ctx);
|
|
997
1211
|
im.style.position = "absolute";
|
|
998
1212
|
im.style.inset = "0";
|
package/studio/web/render.css
CHANGED
|
@@ -65,6 +65,23 @@
|
|
|
65
65
|
--c-fundo: #ffffff; --c-fundo2: #eeeeee; --c-texto: #000000; --c-fraco: #666666;
|
|
66
66
|
--c-tom: #000000; --c-tom2: #000000; --c-linha: #000000; --c-cartao: #f2f2f2;
|
|
67
67
|
}
|
|
68
|
+
/* "âmbar" — o marrom quente com dourado, que é a cara de curso e de aula.
|
|
69
|
+
Entrou porque faltava: os dez temas tinham roxo, ciano, laranja de fogo e
|
|
70
|
+
verde, e nenhum tom terroso onde o texto branco descansa. É o tema que
|
|
71
|
+
sustenta cena longa sem cansar a vista. */
|
|
72
|
+
[data-tema="ambar"] {
|
|
73
|
+
--f-display: "Primo Geo", "Space Grotesk", system-ui, sans-serif;
|
|
74
|
+
--c-fundo: #1a100b; --c-fundo2: #2a1710; --c-texto: #f7efe8; --c-fraco: #b8a08f;
|
|
75
|
+
--c-tom: #e8a33d; --c-tom2: #d0703a; --c-linha: rgba(232, 163, 61, .22); --c-cartao: rgba(232, 163, 61, .07);
|
|
76
|
+
}
|
|
77
|
+
/* "grafite" — o cinza-azulado sóbrio, para conteúdo técnico e relatório.
|
|
78
|
+
Sem cor de festa: quando o assunto é número, a cor não pode competir. */
|
|
79
|
+
[data-tema="grafite"] {
|
|
80
|
+
--f-display: "Primo Geo", "Space Grotesk", system-ui, sans-serif;
|
|
81
|
+
--c-fundo: #0d1117; --c-fundo2: #161d26; --c-texto: #e6edf3; --c-fraco: #8b97a6;
|
|
82
|
+
--c-tom: #4a9eff; --c-tom2: #7ee0c0; --c-linha: rgba(255, 255, 255, .1); --c-cartao: rgba(255, 255, 255, .04);
|
|
83
|
+
}
|
|
84
|
+
|
|
68
85
|
[data-tema="mono"] .el.no,
|
|
69
86
|
[data-tema="mono"] .el.cartoes .cartao { border-width: 3px; border-radius: 0; }
|
|
70
87
|
[data-tema="mono"] .el.txt { letter-spacing: -.04em; }
|
|
@@ -382,6 +399,259 @@
|
|
|
382
399
|
.el.cartoes .cartao b { font-family: var(--f-display); font-size: 27px; font-weight: 620; letter-spacing: -.02em; }
|
|
383
400
|
.el.cartoes .cartao span { font-size: 19px; line-height: 1.45; color: var(--c-fraco); }
|
|
384
401
|
|
|
402
|
+
/* ═══════════════════════════════════════════════════════════════════════
|
|
403
|
+
O vocabulário do vídeo de referência.
|
|
404
|
+
|
|
405
|
+
Tudo aqui existe porque o dono mandou um vídeo e disse "pega tudo que tem
|
|
406
|
+
esse vídeo, e coloca pro Primo poder fazer também" — cartão com relevo,
|
|
407
|
+
vidro, foto, celular com conversa dentro.
|
|
408
|
+
|
|
409
|
+
O fio comum é o VIDRO: painel translúcido, borda clara em cima (onde a luz
|
|
410
|
+
bateria) e sombra embaixo. Não é enfeite — é o que dá profundidade sem
|
|
411
|
+
precisar de imagem, e o que separa "cartão" de "retângulo com borda".
|
|
412
|
+
═══════════════════════════════════════════════════════════════════════ */
|
|
413
|
+
|
|
414
|
+
.vidro, .el.cartoes[data-estilo="vidro"] .cartao, .el.passos .passo {
|
|
415
|
+
background:
|
|
416
|
+
linear-gradient(180deg, rgba(255, 255, 255, .075), rgba(255, 255, 255, .015)),
|
|
417
|
+
var(--c-cartao);
|
|
418
|
+
border: 1px solid var(--c-linha);
|
|
419
|
+
/* A luz de cima e a sombra de baixo, dentro da própria caixa. É o truque
|
|
420
|
+
inteiro do "liquid glass": a borda superior clara finge um bisel. */
|
|
421
|
+
box-shadow:
|
|
422
|
+
inset 0 1px 0 rgba(255, 255, 255, .13),
|
|
423
|
+
0 18px 44px rgba(0, 0, 0, .34);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* ── selo ──────────────────────────────────────────────────────────────── */
|
|
427
|
+
.el.selo {
|
|
428
|
+
display: inline-flex; align-items: center; gap: .5em;
|
|
429
|
+
align-self: flex-start;
|
|
430
|
+
font-family: var(--f-corpo);
|
|
431
|
+
font-size: 21px; font-weight: 620;
|
|
432
|
+
letter-spacing: .13em; text-transform: uppercase;
|
|
433
|
+
color: var(--c-tom);
|
|
434
|
+
background: color-mix(in srgb, var(--c-tom) 13%, transparent);
|
|
435
|
+
border: 1px solid color-mix(in srgb, var(--c-tom) 42%, transparent);
|
|
436
|
+
border-radius: 999px;
|
|
437
|
+
padding: .42em 1.05em;
|
|
438
|
+
}
|
|
439
|
+
.el.selo[data-estilo="vivo"] {
|
|
440
|
+
color: var(--c-fundo);
|
|
441
|
+
background: var(--c-tom);
|
|
442
|
+
border-color: var(--c-tom);
|
|
443
|
+
}
|
|
444
|
+
.el.selo[data-estilo="solto"] {
|
|
445
|
+
background: none; border-color: transparent; padding-left: 0; padding-right: 0;
|
|
446
|
+
}
|
|
447
|
+
.el.selo .selo-icone { font-size: 1.15em; letter-spacing: 0; }
|
|
448
|
+
.cena .fluxo[data-layout="capa"] > .el.selo,
|
|
449
|
+
.cena .fluxo[data-layout="centro"] > .el.selo { align-self: center; }
|
|
450
|
+
|
|
451
|
+
/* ── progresso ─────────────────────────────────────────────────────────── */
|
|
452
|
+
.el.progresso {
|
|
453
|
+
display: flex; align-items: center; gap: 18px;
|
|
454
|
+
color: var(--c-tom); font-family: var(--f-corpo);
|
|
455
|
+
}
|
|
456
|
+
.el.progresso > b { font-size: 24px; font-weight: 620; letter-spacing: .04em; }
|
|
457
|
+
.el.progresso .pontos { display: flex; align-items: center; gap: 9px; }
|
|
458
|
+
.el.progresso .pontos i {
|
|
459
|
+
width: 9px; height: 9px; border-radius: 50%;
|
|
460
|
+
background: var(--c-linha);
|
|
461
|
+
transition: none;
|
|
462
|
+
}
|
|
463
|
+
.el.progresso .pontos i.feito { background: color-mix(in srgb, var(--c-tom) 45%, transparent); }
|
|
464
|
+
.el.progresso .pontos i.aqui {
|
|
465
|
+
background: var(--c-tom); width: 12px; height: 12px;
|
|
466
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--c-tom) 18%, transparent);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* ── avatar ────────────────────────────────────────────────────────────── */
|
|
470
|
+
.el.avatar {
|
|
471
|
+
border-radius: 50%; overflow: hidden; position: relative;
|
|
472
|
+
display: grid; place-items: center; flex: 0 0 auto;
|
|
473
|
+
background: var(--c-cartao);
|
|
474
|
+
/* O anel aceso: dois anéis, um forte colado e um aberto e fraco. Um só
|
|
475
|
+
fica duro; dois viram luz. */
|
|
476
|
+
box-shadow:
|
|
477
|
+
0 0 0 3px var(--c-tom),
|
|
478
|
+
0 0 0 12px color-mix(in srgb, var(--c-tom) 16%, transparent),
|
|
479
|
+
0 22px 50px rgba(0, 0, 0, .42);
|
|
480
|
+
}
|
|
481
|
+
.el.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
482
|
+
.el.avatar b {
|
|
483
|
+
font-family: var(--f-display); font-weight: 640;
|
|
484
|
+
color: var(--c-tom); letter-spacing: .02em;
|
|
485
|
+
}
|
|
486
|
+
.cena .fluxo[data-layout="capa"] > .el.avatar,
|
|
487
|
+
.cena .fluxo[data-layout="centro"] > .el.avatar { align-self: center; }
|
|
488
|
+
|
|
489
|
+
/* ── dispositivo ───────────────────────────────────────────────────────── */
|
|
490
|
+
/* O aparelho ENCOLHE para caber na cena. Preso só pela largura, o celular de
|
|
491
|
+
380px virava 800px de altura e vazava pelo pé do quadro — metade da
|
|
492
|
+
conversa ficava fora do vídeo, e na tela do editor parecia certo porque ali
|
|
493
|
+
a página rola. A altura é que manda; a largura vem da proporção. */
|
|
494
|
+
.el.dispositivo {
|
|
495
|
+
display: grid; align-content: center; justify-items: center;
|
|
496
|
+
flex: 0 1 auto; min-height: 0; max-height: 100%;
|
|
497
|
+
}
|
|
498
|
+
.el.dispositivo .moldura {
|
|
499
|
+
background: linear-gradient(180deg, #22262c, #14171b);
|
|
500
|
+
box-shadow:
|
|
501
|
+
inset 0 1px 0 rgba(255, 255, 255, .16),
|
|
502
|
+
0 30px 70px rgba(0, 0, 0, .5);
|
|
503
|
+
overflow: hidden;
|
|
504
|
+
display: flex; flex-direction: column;
|
|
505
|
+
}
|
|
506
|
+
.el.dispositivo .tela {
|
|
507
|
+
flex: 1; overflow: hidden;
|
|
508
|
+
background: var(--c-fundo2);
|
|
509
|
+
display: flex; flex-direction: column; justify-content: center;
|
|
510
|
+
gap: 16px;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.el.dispositivo[data-forma="celular"] { width: 100%; }
|
|
514
|
+
.el.dispositivo[data-forma="celular"] .moldura {
|
|
515
|
+
height: min(100%, 780px); width: auto; max-width: 100%;
|
|
516
|
+
aspect-ratio: 9 / 19;
|
|
517
|
+
border-radius: 42px; padding: 12px;
|
|
518
|
+
}
|
|
519
|
+
.el.dispositivo[data-forma="celular"] .tela { border-radius: 31px; padding: 26px 20px; }
|
|
520
|
+
/* A ilha da câmera. Sem ela um retângulo arredondado é só um retângulo
|
|
521
|
+
arredondado — é este detalhe que faz o olho ler "celular". */
|
|
522
|
+
.el.dispositivo[data-forma="celular"] .moldura::before {
|
|
523
|
+
content: ""; position: absolute; left: 50%; top: 20px;
|
|
524
|
+
width: 88px; height: 9px; border-radius: 999px;
|
|
525
|
+
background: rgba(0, 0, 0, .55); transform: translateX(-50%); z-index: 2;
|
|
526
|
+
}
|
|
527
|
+
.el.dispositivo[data-forma="celular"] .moldura { position: relative; }
|
|
528
|
+
|
|
529
|
+
.el.dispositivo[data-forma="tablet"] .moldura {
|
|
530
|
+
height: min(100%, 740px); width: auto; max-width: 100%;
|
|
531
|
+
aspect-ratio: 3 / 4;
|
|
532
|
+
border-radius: 26px; padding: 16px;
|
|
533
|
+
}
|
|
534
|
+
.el.dispositivo[data-forma="tablet"] .tela { border-radius: 12px; padding: 26px; }
|
|
535
|
+
|
|
536
|
+
.el.dispositivo[data-forma="notebook"] { width: 100%; }
|
|
537
|
+
.el.dispositivo[data-forma="notebook"] .moldura {
|
|
538
|
+
width: 100%; max-height: 100%; aspect-ratio: 16 / 10;
|
|
539
|
+
border-radius: 16px 16px 4px 4px; padding: 12px 12px 14px;
|
|
540
|
+
}
|
|
541
|
+
.el.dispositivo[data-forma="notebook"] .tela { border-radius: 7px; padding: 26px 28px; }
|
|
542
|
+
.el.dispositivo[data-forma="notebook"] .base {
|
|
543
|
+
width: 116%; height: 13px; margin-top: -1px;
|
|
544
|
+
border-radius: 0 0 12px 12px;
|
|
545
|
+
background: linear-gradient(180deg, #2c3138, #171a1e);
|
|
546
|
+
box-shadow: 0 12px 26px rgba(0, 0, 0, .45);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.el.dispositivo[data-forma="navegador"] { width: 100%; }
|
|
550
|
+
.el.dispositivo[data-forma="navegador"] .moldura {
|
|
551
|
+
width: 100%; max-height: 100%; aspect-ratio: 16 / 10; border-radius: 14px; padding: 0;
|
|
552
|
+
}
|
|
553
|
+
.el.dispositivo .barra-nav {
|
|
554
|
+
display: flex; align-items: center; gap: 8px;
|
|
555
|
+
padding: 13px 16px; flex: 0 0 auto;
|
|
556
|
+
border-bottom: 1px solid rgba(255, 255, 255, .07);
|
|
557
|
+
}
|
|
558
|
+
.el.dispositivo .barra-nav i {
|
|
559
|
+
width: 11px; height: 11px; border-radius: 50%; background: rgba(255, 255, 255, .17);
|
|
560
|
+
}
|
|
561
|
+
.el.dispositivo .barra-nav .url {
|
|
562
|
+
margin-left: 12px; flex: 1;
|
|
563
|
+
font-family: var(--f-mono); font-size: 15px; color: rgba(255, 255, 255, .45);
|
|
564
|
+
background: rgba(0, 0, 0, .3); border-radius: 999px; padding: 5px 14px;
|
|
565
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
566
|
+
}
|
|
567
|
+
.el.dispositivo[data-forma="navegador"] .tela { padding: 24px 26px; }
|
|
568
|
+
|
|
569
|
+
/* ── conversa ──────────────────────────────────────────────────────────── */
|
|
570
|
+
.el.conversa {
|
|
571
|
+
display: flex; flex-direction: column; gap: 14px; justify-content: flex-end;
|
|
572
|
+
}
|
|
573
|
+
.el.conversa .balao {
|
|
574
|
+
max-width: 88%; padding: 13px 17px; line-height: 1.42;
|
|
575
|
+
font-size: 20px; border-radius: 16px;
|
|
576
|
+
}
|
|
577
|
+
.el.conversa .balao.meu {
|
|
578
|
+
align-self: flex-start;
|
|
579
|
+
background: var(--c-cartao); border: 1px solid var(--c-linha);
|
|
580
|
+
color: var(--c-texto); border-bottom-left-radius: 5px;
|
|
581
|
+
}
|
|
582
|
+
/* A resposta é o que importa: ela leva a cor do tema. */
|
|
583
|
+
.el.conversa .balao.dela {
|
|
584
|
+
align-self: flex-end;
|
|
585
|
+
background: color-mix(in srgb, var(--c-tom) 20%, transparent);
|
|
586
|
+
border: 1px solid color-mix(in srgb, var(--c-tom) 40%, transparent);
|
|
587
|
+
color: var(--c-texto); border-bottom-right-radius: 5px;
|
|
588
|
+
}
|
|
589
|
+
/* Os balões entram um de cada vez, como numa conversa de verdade. Todos ao
|
|
590
|
+
mesmo tempo lê como captura de tela, não como diálogo. */
|
|
591
|
+
.cena.animar .el.conversa .balao {
|
|
592
|
+
animation: balaoEntra .46s cubic-bezier(.22, 1, .36, 1) both;
|
|
593
|
+
animation-delay: calc(var(--atraso, 0) * 1s);
|
|
594
|
+
}
|
|
595
|
+
@keyframes balaoEntra {
|
|
596
|
+
from { opacity: 0; transform: translateY(14px) scale(.96); }
|
|
597
|
+
to { opacity: 1; transform: none; }
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* ── passos ────────────────────────────────────────────────────────────── */
|
|
601
|
+
/* Sem largura declarada, o grid encolhe até o conteúdo e as três etapas
|
|
602
|
+
viram três selinhos no meio da tela — em `capa` e `centro`, onde o eixo
|
|
603
|
+
está centralizado, isso é o padrão. Etapa é estrutura: ocupa a linha. */
|
|
604
|
+
.el.passos { display: grid; gap: 18px; align-items: stretch; width: 100%; }
|
|
605
|
+
.el.passos .passo {
|
|
606
|
+
border-radius: 18px; padding: 22px 20px;
|
|
607
|
+
display: grid; gap: 6px; align-content: start;
|
|
608
|
+
color: var(--c-fraco);
|
|
609
|
+
}
|
|
610
|
+
.el.passos .passo b {
|
|
611
|
+
font-family: var(--f-display); font-size: 23px; font-weight: 620;
|
|
612
|
+
letter-spacing: -.015em; color: inherit;
|
|
613
|
+
}
|
|
614
|
+
.el.passos .passo span { font-size: 17px; line-height: 1.4; }
|
|
615
|
+
.el.passos .passo.feito { color: var(--c-texto); }
|
|
616
|
+
/* O de agora cresce e acende. Escala pequena de propósito: 1.06 lê como
|
|
617
|
+
"este", 1.2 lê como erro de layout. */
|
|
618
|
+
.el.passos .passo.agora {
|
|
619
|
+
color: var(--c-texto);
|
|
620
|
+
transform: scale(1.06);
|
|
621
|
+
border-color: color-mix(in srgb, var(--c-tom) 55%, transparent);
|
|
622
|
+
background:
|
|
623
|
+
linear-gradient(180deg, color-mix(in srgb, var(--c-tom) 17%, transparent),
|
|
624
|
+
color-mix(in srgb, var(--c-tom) 6%, transparent));
|
|
625
|
+
box-shadow:
|
|
626
|
+
inset 0 1px 0 rgba(255, 255, 255, .16),
|
|
627
|
+
0 20px 46px color-mix(in srgb, var(--c-tom) 16%, transparent);
|
|
628
|
+
}
|
|
629
|
+
.el.passos .passo.agora b { color: var(--c-tom); }
|
|
630
|
+
|
|
631
|
+
/* ── opcoes ────────────────────────────────────────────────────────────── */
|
|
632
|
+
.el.opcoes { display: grid; gap: 12px; align-content: center; }
|
|
633
|
+
.el.opcoes .pergunta {
|
|
634
|
+
font-family: var(--f-display); font-size: 26px; font-weight: 640;
|
|
635
|
+
letter-spacing: -.018em; margin-bottom: 6px; color: var(--c-texto);
|
|
636
|
+
}
|
|
637
|
+
.el.opcoes .opcao {
|
|
638
|
+
border: 1px solid var(--c-linha); border-radius: 12px;
|
|
639
|
+
padding: 14px 18px; font-size: 19px; color: var(--c-fraco);
|
|
640
|
+
background: var(--c-cartao);
|
|
641
|
+
}
|
|
642
|
+
.el.opcoes .opcao.certa {
|
|
643
|
+
color: var(--c-tom);
|
|
644
|
+
border-color: color-mix(in srgb, var(--c-tom) 60%, transparent);
|
|
645
|
+
background: color-mix(in srgb, var(--c-tom) 12%, transparent);
|
|
646
|
+
}
|
|
647
|
+
/* O ✓ vem do CSS, não do texto: assim o autor escreve só a alternativa e a
|
|
648
|
+
marca nunca aparece por engano na errada. */
|
|
649
|
+
.el.opcoes .opcao.certa::before { content: "✓ "; font-weight: 700; }
|
|
650
|
+
.cena.animar .el.opcoes .opcao {
|
|
651
|
+
animation: balaoEntra .4s cubic-bezier(.22, 1, .36, 1) both;
|
|
652
|
+
animation-delay: calc(var(--atraso, 0) * 1s);
|
|
653
|
+
}
|
|
654
|
+
|
|
385
655
|
.el.numeros { display: grid; gap: calc(30px * var(--esc, 1)); align-content: center; }
|
|
386
656
|
.el.numeros .num { display: grid; gap: 6px; }
|
|
387
657
|
.el.numeros .num b {
|
|
@@ -656,3 +926,10 @@
|
|
|
656
926
|
|
|
657
927
|
/* painel: sem moldura, é uma superfície flutuando em profundidade */
|
|
658
928
|
.el.obj3d.f-painel .face3d { border: 0; box-shadow: 0 40px 90px -30px rgba(0,0,0,.7); }
|
|
929
|
+
|
|
930
|
+
/* O lado do aparelho não é o lado da imagem: a imagem sangra até a borda, o
|
|
931
|
+
celular precisa de ar em volta para a moldura e a sombra aparecerem. */
|
|
932
|
+
.cena .fluxo .lado-imagem.com-aparelho {
|
|
933
|
+
display: grid; place-items: center; min-height: 0; overflow: visible;
|
|
934
|
+
}
|
|
935
|
+
.cena .fluxo .lado-imagem.com-aparelho > .el { max-height: 100%; }
|