primocode 8.38.0 → 8.39.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.
@@ -188,10 +188,18 @@
188
188
  "click" | "nenhum") e ganha do automático por transição. */
189
189
  if (sfx && tocando && ehVideo && atual !== anterior && doc.sfx !== false) {
190
190
  var cenaNova = cenas[atual] || {};
191
- if (cenaNova.sfx !== "nenhum") {
192
- if (cenaNova.sfx && sfx[cenaNova.sfx]) sfx[cenaNova.sfx]();
191
+ var somDaCena = cenaNova.som != null ? cenaNova.som : cenaNova.sfx;
192
+ if (somDaCena !== "nenhum") {
193
+ if (somDaCena) sfx.tocar(somDaCena);
193
194
  else sfx.transicao(cenaNova.transicao, atual);
194
195
  }
196
+ /* SOM POR ELEMENTO: `som` num elemento toca quando ELE entra — no
197
+ mesmo instante da animação de entrada (o mesmo atraso da cascata
198
+ que o render.js dá ao --atraso). Um número que faz "ding" ao
199
+ aparecer, um ícone que faz "pop", a barra que sobe com o riser: é
200
+ o que separa motion de slide animado. Nome da biblioteca ou
201
+ {arquivo, volume} — o arquivo entra no grafo e vai para o MP4. */
202
+ agendarSonsDosElementos(cenaNova);
195
203
  }
196
204
  narrar();
197
205
  pintarApresentador();
@@ -507,6 +515,26 @@
507
515
 
508
516
  var sfx = null;
509
517
 
518
+ /* Os sons dos ELEMENTOS da cena que acabou de entrar. O atraso é o mesmo
519
+ que o render.js dá à animação de entrada (cascata: i × 0,09 s + 0,1 s, ou
520
+ o `anim.atraso` declarado), então o som cai no instante em que o
521
+ elemento aparece. Trocar de cena antes cancela o que ainda não tocou —
522
+ senão o "ding" da cena 3 tocava em cima da cena 4. */
523
+ var sonsAgendados = [];
524
+ function agendarSonsDosElementos(cena) {
525
+ sonsAgendados.forEach(clearTimeout);
526
+ sonsAgendados = [];
527
+ if (!sfx || !cena || !Array.isArray(cena.elementos)) return;
528
+ cena.elementos.forEach(function (e, i) {
529
+ if (!e || !e.som) return;
530
+ var atraso = (e.anim && typeof e.anim === "object" && e.anim.atraso != null)
531
+ ? +e.anim.atraso
532
+ : (e.chaves && e.chaves.length > 1 ? 0 : i * 0.09 + 0.1);
533
+ if (typeof e.som === "object" && e.som.em != null) atraso = +e.som.em; // segundos na cena, explícito
534
+ sonsAgendados.push(setTimeout(function () { sfx.tocar(e.som); }, Math.max(0, atraso) * 1000));
535
+ });
536
+ }
537
+
510
538
  function prepararTrilha() {
511
539
  if (typeof criarTrilha !== "function") return;
512
540
  var cfg = doc.trilha;
@@ -279,8 +279,14 @@ function aplicarChaves(no, e, ctx) {
279
279
  // espera: "o que eu não toquei, fica como estava". O primeiro valor de
280
280
  // cada campo, quando nem a primeira chave o declara, é o neutro (escala e
281
281
  // opacidade em 1, deslocamento e giro em 0) — nunca zero para escala.
282
- var NEUTRO = { x: 0, y: 0, escala: 1, giro: 0, opacidade: 1, desfoque: 0 };
283
- var CAMPOS = ["x", "y", "escala", "giro", "opacidade", "desfoque"];
282
+ // giroX/giroY/prof são o 3D: viram rotateX/rotateY/translateZ com uma
283
+ // perspectiva na frente. É o que faz um cartão virar de verdade em vez de
284
+ // só encolher — pedido do dono: "quero que a IA faça coisas 3D, mexa com
285
+ // keyframes". escalaX/escalaY esticam num eixo só (a "gota" que se
286
+ // achata ao pousar). `prof`, não `z`: `z` no elemento já é a ordem das
287
+ // camadas (zIndex), e o mesmo nome com dois sentidos é armadilha.
288
+ var NEUTRO = { x: 0, y: 0, escala: 1, escalaX: 1, escalaY: 1, giro: 0, giroX: 0, giroY: 0, prof: 0, opacidade: 1, desfoque: 0 };
289
+ var CAMPOS = ["x", "y", "escala", "escalaX", "escalaY", "giro", "giroX", "giroY", "prof", "opacidade", "desfoque"];
284
290
  var herdado = {};
285
291
  CAMPOS.forEach(function (c) {
286
292
  // semente: primeiro valor declarado em qualquer chave, senão o neutro.
@@ -301,8 +307,17 @@ function aplicarChaves(no, e, ctx) {
301
307
  // Assim a chave continua valendo se a pessoa arrastar o elemento no
302
308
  // palco depois — o contrário obrigaria a refazer todas as chaves.
303
309
  var dx = herdado.x, dy = herdado.y;
310
+ var tem3d = herdado.giroX || herdado.giroY || herdado.prof;
311
+ // A perspectiva vai NA transformação, não no pai: o pai é a cena, e uma
312
+ // perspectiva lá mudaria todos os elementos. Constante em todas as
313
+ // chaves, para o navegador interpolar só o que muda.
314
+ if (tem3d) mov.push("perspective(1200px)");
304
315
  if (dx || dy) mov.push("translate(" + dx + "px," + dy + "px)");
316
+ if (herdado.prof) mov.push("translateZ(" + herdado.prof + "px)");
305
317
  if (herdado.escala !== 1) mov.push("scale(" + herdado.escala + ")");
318
+ if (herdado.escalaX !== 1 || herdado.escalaY !== 1) mov.push("scale(" + herdado.escalaX + "," + herdado.escalaY + ")");
319
+ if (herdado.giroX) mov.push("rotateX(" + herdado.giroX + "deg)");
320
+ if (herdado.giroY) mov.push("rotateY(" + herdado.giroY + "deg)");
306
321
  if (herdado.giro) mov.push("rotate(" + herdado.giro + "deg)");
307
322
 
308
323
  var linhas = ["transform:" + (mov.length ? mov.join(" ") : "none")];
@@ -328,9 +343,38 @@ function aplicarChaves(no, e, ctx) {
328
343
  var dur = _numero(ctx.duracao, 4);
329
344
  no.style.animationName = nome;
330
345
  no.style.animationDuration = dur + "s";
331
- no.style.animationTimingFunction = e.suavizar === false ? "linear" : "cubic-bezier(.4,0,.2,1)";
346
+ no.style.animationTimingFunction = curvaDe(e.suavizar);
332
347
  no.style.animationFillMode = "both";
333
348
  no.style.animationIterationCount = e.repetir ? "infinite" : "1";
349
+ if (e.repetir && e.vaiEVolta) no.style.animationDirection = "alternate";
350
+ if (tem3dEm(chaves)) no.style.transformStyle = "preserve-3d";
351
+ }
352
+
353
+ function tem3dEm(chaves) {
354
+ for (var i = 0; i < chaves.length; i++) {
355
+ if (chaves[i].giroX || chaves[i].giroY || chaves[i].prof) return true;
356
+ }
357
+ return false;
358
+ }
359
+
360
+ /* As curvas com NOME. `suavizar: false` continua sendo linear; o padrão
361
+ continua sendo a curva suave. Os nomes existem porque "elástico" e "salto"
362
+ são o que separa motion de slide animado, e pedir cubic-bezier a quem
363
+ escreve JSON é pedir chute. */
364
+ var CURVAS = {
365
+ suave: "cubic-bezier(.4,0,.2,1)",
366
+ linear: "linear",
367
+ entrada: "cubic-bezier(.4,0,1,1)", // acelera: sai devagar, chega rápido
368
+ saida: "cubic-bezier(0,0,.2,1)", // freia: chega devagar
369
+ elastico: "cubic-bezier(.34,1.56,.64,1)", // passa do ponto e volta
370
+ salto: "cubic-bezier(.68,-.55,.27,1.55)", // recua, dispara, passa e assenta
371
+ degrau: "steps(6, end)", // quadro a quadro, para o "stop motion"
372
+ };
373
+ function curvaDe(suavizar) {
374
+ if (suavizar === false) return CURVAS.linear;
375
+ if (typeof suavizar === "string" && CURVAS[suavizar]) return CURVAS[suavizar];
376
+ if (typeof suavizar === "string" && /^(cubic-bezier|steps)\(/.test(suavizar)) return suavizar;
377
+ return CURVAS.suave;
334
378
  }
335
379
 
336
380
  /* ── identidade da marca ─────────────────────────────────────────────────
@@ -527,6 +571,14 @@ function renderElemento(e, ctx) {
527
571
  if (e.opacidade != null) no.style.opacity = e.opacidade;
528
572
  if (e.sombra) no.style.boxShadow = e.sombra === true ? "0 30px 70px -25px rgba(0,0,0,.6)" : e.sombra;
529
573
  if (e.z != null) no.style.zIndex = e.z;
574
+ /* MATERIAL: "vidro" é o liquid glass — translúcido, desfocando o que está
575
+ atrás, com bisel de luz em cima e o reflexo diagonal. Vale em QUALQUER
576
+ elemento (texto, forma, ícone, imagem, lista, cartões), por isso mora
577
+ aqui e não num tipo. O CSS faz o resto por `data-material`. */
578
+ if (e.material) {
579
+ no.dataset.material = e.material;
580
+ if (e.raio != null) no.style.borderRadius = _px(e.raio);
581
+ }
530
582
 
531
583
  /* animação */
532
584
  var an = e.anim || ctx.animPadrao;
@@ -882,7 +934,14 @@ function renderElemento(e, ctx) {
882
934
 
883
935
  case "forma": {
884
936
  var f = e.forma || "retangulo";
885
- no.style.background = e.preenchimento || "var(--c-tom)";
937
+ // Forma de vidro: o `preenchimento` vira a TINTA sob o vidro (fraca),
938
+ // e o vidro em si é do CSS. Pintar o fundo inteiro aqui cobriria o
939
+ // desfoque — e um vidro opaco é um retângulo.
940
+ if (e.material === "vidro") {
941
+ if (e.preenchimento) no.style.setProperty("--tinta", e.preenchimento);
942
+ } else {
943
+ no.style.background = e.preenchimento || "var(--c-tom)";
944
+ }
886
945
  if (e.borda) no.style.border = (e.borda.largura || 2) + "px solid " + (e.borda.cor || "var(--c-linha)");
887
946
  if (f === "circulo") no.style.borderRadius = "999px";
888
947
  else no.style.borderRadius = _px(e.raio != null ? e.raio : 0);
@@ -1182,6 +1241,17 @@ function renderElemento(e, ctx) {
1182
1241
  no.style.width = _px(e.l || t);
1183
1242
  no.style.height = _px(e.a || t);
1184
1243
  no.style.color = e.cor || "var(--c-tom)";
1244
+ /* ÍCONE COMO OBJETO: `estilo: "app"` é o azulejo de aplicativo —
1245
+ gradiente da cor, brilho em cima, sombra embaixo, o traço branco —
1246
+ e `estilo: "vidro"` é o mesmo azulejo em liquid glass. Pedido do
1247
+ dono ("icon, liquid glass, bem motion"): um ícone de traço sozinho
1248
+ é sinal; num azulejo vira coisa. `fundo` pinta o azulejo; sem ele, a
1249
+ cor do tema. */
1250
+ if (e.estilo === "app" || e.estilo === "vidro") {
1251
+ no.dataset.estilo = e.estilo;
1252
+ no.style.setProperty("--azulejo", e.fundo || e.cor || "var(--c-tom)");
1253
+ if (e.estilo === "app") no.style.color = e.corTraco || "#fff";
1254
+ }
1185
1255
  // `caminho`: o ícone desenhado pelo modelo (path SVG 24×24) — ver
1186
1256
  // icones.js. Sem ele, um da lista pelo nome.
1187
1257
  no.innerHTML = e.caminho
@@ -85,6 +85,110 @@ function criarSfx(ctx, destino) {
85
85
  o.start(agora); o.stop(agora + 0.07);
86
86
  }
87
87
 
88
+ /* ── A biblioteca cresceu porque três sons não fazem motion ───────────
89
+ Pedido do dono: "efeitos sonoros… bem motion mesmo". Whoosh, impacto e
90
+ click cobrem a transição; o que entra num elemento — um número que
91
+ aparece, um ícone que pousa, uma barra que sobe — pedia mais. Todos
92
+ sintetizados, sem arquivo e sem licença, calibrados para ficar SOB a
93
+ narração. */
94
+
95
+ /* Bolha estourando: um seno que sobe rápido e some. Entrada de item. */
96
+ function pop() {
97
+ var agora = ctx.currentTime;
98
+ var o = ctx.createOscillator(), g = ctx.createGain();
99
+ o.type = "sine";
100
+ o.frequency.setValueAtTime(380, agora);
101
+ o.frequency.exponentialRampToValueAtTime(900, agora + 0.06);
102
+ g.gain.setValueAtTime(0.0001, agora);
103
+ g.gain.exponentialRampToValueAtTime(0.16, agora + 0.012);
104
+ g.gain.exponentialRampToValueAtTime(0.0001, agora + 0.14);
105
+ o.connect(g); g.connect(saida);
106
+ o.start(agora); o.stop(agora + 0.16);
107
+ }
108
+
109
+ /* Sino de notificação: dois parciais (fundamental + quinta acima), cauda
110
+ longa. Confirmação, número final, "pronto". */
111
+ function ding() {
112
+ var agora = ctx.currentTime;
113
+ [[1320, 0.11], [1980, 0.05], [2640, 0.025]].forEach(function (p, i) {
114
+ var o = ctx.createOscillator(), g = ctx.createGain();
115
+ o.type = "sine";
116
+ o.frequency.value = p[0];
117
+ g.gain.setValueAtTime(0.0001, agora);
118
+ g.gain.exponentialRampToValueAtTime(p[1], agora + 0.008);
119
+ g.gain.exponentialRampToValueAtTime(0.0001, agora + 0.9 - i * 0.15);
120
+ o.connect(g); g.connect(saida);
121
+ o.start(agora); o.stop(agora + 1.0);
122
+ });
123
+ }
124
+
125
+ /* Subida (riser): ruído filtrado que sobe por um segundo e meio e para
126
+ seco — a tensão antes do número grande ou da assinatura. */
127
+ function subida() {
128
+ var agora = ctx.currentTime;
129
+ var s = ctx.createBufferSource(), g = ctx.createGain(), f = ctx.createBiquadFilter();
130
+ s.buffer = bufferRuido();
131
+ s.loop = true;
132
+ f.type = "bandpass"; f.Q.value = 2.2;
133
+ f.frequency.setValueAtTime(180, agora);
134
+ f.frequency.exponentialRampToValueAtTime(3800, agora + 1.5);
135
+ g.gain.setValueAtTime(0.0001, agora);
136
+ g.gain.exponentialRampToValueAtTime(0.13, agora + 1.4);
137
+ g.gain.setValueAtTime(0.0001, agora + 1.52);
138
+ s.connect(f); f.connect(g); g.connect(saida);
139
+ s.start(agora); s.stop(agora + 1.6);
140
+ }
141
+
142
+ /* Tique de relógio/teclado: madeira seca. Passo de lista, contagem. */
143
+ function tique() {
144
+ var agora = ctx.currentTime;
145
+ var s = ctx.createBufferSource(), g = ctx.createGain(), f = ctx.createBiquadFilter();
146
+ s.buffer = bufferRuido();
147
+ f.type = "highpass"; f.frequency.value = 2600;
148
+ g.gain.setValueAtTime(0.14, agora);
149
+ g.gain.exponentialRampToValueAtTime(0.0001, agora + 0.04);
150
+ s.connect(f); f.connect(g); g.connect(saida);
151
+ s.start(agora); s.stop(agora + 0.05);
152
+ }
153
+
154
+ /* Brilho: arpejo rápido e agudo, o "sparkle" de um destaque ou de uma
155
+ estrela acendendo. */
156
+ function brilho() {
157
+ var agora = ctx.currentTime;
158
+ [1568, 2093, 2637, 3136].forEach(function (hz, i) {
159
+ var o = ctx.createOscillator(), g = ctx.createGain();
160
+ o.type = "triangle";
161
+ o.frequency.value = hz;
162
+ var t = agora + i * 0.045;
163
+ g.gain.setValueAtTime(0.0001, t);
164
+ g.gain.exponentialRampToValueAtTime(0.06, t + 0.01);
165
+ g.gain.exponentialRampToValueAtTime(0.0001, t + 0.35);
166
+ o.connect(g); g.connect(saida);
167
+ o.start(t); o.stop(t + 0.4);
168
+ });
169
+ }
170
+
171
+ /* Um ARQUIVO de som (o efeito baixado da internet pelo agente, ou o do
172
+ usuário), tocado dentro do MESMO grafo — é o que faz ele entrar no MP4
173
+ exportado junto da trilha. Decodificado uma vez por URL. */
174
+ var cacheArquivos = {};
175
+ function arquivo(url, volume) {
176
+ if (!url) return;
177
+ var pronto = cacheArquivos[url] || (cacheArquivos[url] = fetch(url)
178
+ .then(function (r) { return r.arrayBuffer(); })
179
+ .then(function (b) { return ctx.decodeAudioData(b); })
180
+ .catch(function () { return null; }));
181
+ pronto.then(function (buffer) {
182
+ if (!buffer) return;
183
+ var s = ctx.createBufferSource(), g = ctx.createGain();
184
+ s.buffer = buffer;
185
+ g.gain.value = volume == null ? 0.7 : Math.max(0, Math.min(1, volume));
186
+ s.connect(g); g.connect(saida);
187
+ s.start(ctx.currentTime);
188
+ });
189
+ return pronto;
190
+ }
191
+
88
192
  /* O efeito certo para a ENTRADA de uma cena, pela transição dela.
89
193
  flash e zoom pedem peso; corte seco não pede nada. */
90
194
  function transicao(tipo, indice) {
@@ -93,15 +197,37 @@ function criarSfx(ctx, destino) {
93
197
  whoosh(((indice || 0) * 0.37) % 1);
94
198
  }
95
199
 
96
- return {
200
+ /* Toca pelo NOME ou por {arquivo, volume}. É o que o player chama para o
201
+ `som` de um elemento ou de uma cena. Nome desconhecido não toca nada —
202
+ e não quebra. */
203
+ function tocar(som) {
204
+ if (!som) return;
205
+ if (typeof som === "string") { if (typeof api[som] === "function" && som !== "tocar") api[som](); return; }
206
+ if (som.arquivo) return arquivo(som.arquivo, som.volume);
207
+ if (som.nome && typeof api[som.nome] === "function") api[som.nome]();
208
+ }
209
+
210
+ var api = {
97
211
  whoosh: whoosh,
98
212
  impacto: impacto,
99
213
  click: click,
214
+ pop: pop,
215
+ ding: ding,
216
+ subida: subida,
217
+ tique: tique,
218
+ brilho: brilho,
219
+ arquivo: arquivo,
220
+ tocar: tocar,
100
221
  transicao: transicao,
101
222
  volume: function (v) { saida.gain.value = v; },
102
223
  };
224
+ return api;
103
225
  }
104
226
 
227
+ /* Os nomes que o documento pode usar em `som`/`sfx` — lidos pela spec e pelo
228
+ servidor, para recusar nome inventado com a lista certa. */
229
+ var SONS = ["whoosh", "impacto", "click", "pop", "ding", "subida", "tique", "brilho"];
230
+
105
231
  if (typeof module !== "undefined" && module.exports) {
106
- module.exports = { criarSfx: criarSfx };
232
+ module.exports = { criarSfx: criarSfx, SONS: SONS };
107
233
  }
@@ -423,6 +423,101 @@
423
423
  0 18px 44px rgba(0, 0, 0, .34);
424
424
  }
425
425
 
426
+ /* ── MATERIAL: vidro (liquid glass) ─────────────────────────────────────
427
+ Pedido do dono: "liquid glass, icon, bem motion". O que faz o vidro ser
428
+ vidro, e não um retângulo translúcido:
429
+ · DESFOCA o que está atrás (backdrop-filter) e SATURA — o fundo fica
430
+ vivo através dele, como numa lente;
431
+ · o BISEL: uma linha clara em cima (onde a luz bate) e uma escura
432
+ embaixo, dentro da própria caixa;
433
+ · o REFLEXO diagonal (::before), que no vídeo atravessa devagar — é o
434
+ "líquido" do nome;
435
+ · a TINTA opcional (--tinta), fraca, para o vidro ter cor sem ficar opaco.
436
+ Vale em qualquer elemento: texto vira pílula de vidro, forma vira painel,
437
+ ícone vira azulejo, imagem ganha moldura. ─────────────────────────────── */
438
+ .el[data-material="vidro"] {
439
+ position: absolute;
440
+ background:
441
+ linear-gradient(135deg, rgba(255, 255, 255, .26), rgba(255, 255, 255, .07) 38%,
442
+ rgba(255, 255, 255, .02) 60%, rgba(255, 255, 255, .14)),
443
+ color-mix(in srgb, var(--tinta, transparent) 28%, transparent);
444
+ -webkit-backdrop-filter: blur(22px) saturate(1.65);
445
+ backdrop-filter: blur(22px) saturate(1.65);
446
+ border: 1px solid rgba(255, 255, 255, .30);
447
+ border-radius: 28px;
448
+ box-shadow:
449
+ inset 0 1px 0 rgba(255, 255, 255, .60),
450
+ inset 0 -1px 0 rgba(0, 0, 0, .20),
451
+ inset 0 0 0 1px rgba(255, 255, 255, .05),
452
+ 0 26px 60px -22px rgba(0, 0, 0, .60);
453
+ overflow: hidden;
454
+ }
455
+ .el[data-material="vidro"]::before {
456
+ content: ""; position: absolute; inset: -40% -30%; pointer-events: none;
457
+ background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, .30) 47%,
458
+ rgba(255, 255, 255, .07) 53%, transparent 60%);
459
+ transform: translateX(-28%);
460
+ }
461
+ .cena.animar .el[data-material="vidro"]::before { animation: brilhoVidro 7s cubic-bezier(.4, 0, .2, 1) infinite; }
462
+ @keyframes brilhoVidro {
463
+ 0%, 100% { transform: translateX(-28%); }
464
+ 50% { transform: translateX(28%); }
465
+ }
466
+ .el.txt[data-material="vidro"] { padding: .42em .85em; }
467
+ .el.forma[data-material="vidro"] { background-color: transparent; }
468
+ .el.imagem[data-material="vidro"] { padding: 14px; }
469
+ .el.imagem[data-material="vidro"] img { border-radius: calc(28px - 12px); }
470
+ .el.lista[data-material="vidro"], .el.cartoes[data-material="vidro"], .el.numeros[data-material="vidro"] { padding: 4% 5%; }
471
+ /* Tema claro: o vidro escurece um pouco, senão some no branco. */
472
+ [data-tema="claro"] .el[data-material="vidro"], .tema-claro .el[data-material="vidro"] {
473
+ background:
474
+ linear-gradient(135deg, rgba(255, 255, 255, .62), rgba(255, 255, 255, .30) 40%, rgba(255, 255, 255, .55)),
475
+ color-mix(in srgb, var(--tinta, transparent) 22%, transparent);
476
+ border-color: rgba(0, 0, 0, .10);
477
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), inset 0 -1px 0 rgba(0, 0, 0, .06), 0 26px 60px -24px rgba(0, 0, 0, .28);
478
+ }
479
+
480
+ /* ── ÍCONE COMO OBJETO: o azulejo de aplicativo ──────────────────────────
481
+ `estilo: "app"`: a cor em gradiente com duas luzes, o brilho na metade de
482
+ cima, a sombra colorida embaixo e o traço branco com sombra própria — é a
483
+ gramática de ícone de app, e o olho lê "produto" em vez de "sinal".
484
+ `estilo: "vidro"`: o mesmo azulejo em liquid glass. ─────────────────── */
485
+ .el.icone[data-estilo="app"], .el.icone[data-estilo="vidro"] {
486
+ position: absolute; border-radius: 23.5%; overflow: hidden;
487
+ }
488
+ /* O traço ocupa 58% do azulejo. NÃO por padding em %: padding em
489
+ porcentagem é relativo à LARGURA DO PAI (a cena, 1920px), e 21% virava
490
+ 400px de padding num ícone de 220 — o azulejo engolia o canto da tela e
491
+ o traço sumia. Medido na primeira captura. */
492
+ .el.icone[data-estilo="app"] svg, .el.icone[data-estilo="vidro"] svg { width: 58%; height: 58%; }
493
+ .el.icone[data-estilo="app"] {
494
+ --az: var(--azulejo, var(--c-tom));
495
+ background:
496
+ radial-gradient(130% 110% at 18% 8%, rgba(255, 255, 255, .55), transparent 52%),
497
+ linear-gradient(160deg, color-mix(in srgb, var(--az) 78%, #fff), var(--az) 52%,
498
+ color-mix(in srgb, var(--az) 68%, #000));
499
+ box-shadow:
500
+ inset 0 2px 0 rgba(255, 255, 255, .55),
501
+ inset 0 -3px 0 rgba(0, 0, 0, .28),
502
+ 0 22px 44px -16px color-mix(in srgb, var(--az) 70%, #000);
503
+ }
504
+ .el.icone[data-estilo="app"]::after {
505
+ content: ""; position: absolute; left: 5%; right: 5%; top: 3%; height: 47%; pointer-events: none;
506
+ border-radius: 22% 22% 48% 48% / 30% 30% 60% 60%;
507
+ background: linear-gradient(180deg, rgba(255, 255, 255, .34), rgba(255, 255, 255, .02));
508
+ }
509
+ .el.icone[data-estilo="app"] svg { position: relative; filter: drop-shadow(0 4px 8px rgba(0, 0, 0, .35)); }
510
+ .el.icone[data-estilo="vidro"] {
511
+ --tinta: var(--azulejo, var(--c-tom));
512
+ background:
513
+ linear-gradient(135deg, rgba(255, 255, 255, .28), rgba(255, 255, 255, .07) 40%, rgba(255, 255, 255, .16)),
514
+ color-mix(in srgb, var(--tinta) 30%, transparent);
515
+ -webkit-backdrop-filter: blur(20px) saturate(1.6); backdrop-filter: blur(20px) saturate(1.6);
516
+ border: 1px solid rgba(255, 255, 255, .32);
517
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6), inset 0 -1px 0 rgba(0, 0, 0, .2), 0 24px 50px -20px rgba(0, 0, 0, .55);
518
+ }
519
+ .el.icone[data-estilo="vidro"] svg { position: relative; filter: drop-shadow(0 3px 10px rgba(0, 0, 0, .3)); }
520
+
426
521
  /* ── selo ──────────────────────────────────────────────────────────────── */
427
522
  .el.selo {
428
523
  display: inline-flex; align-items: center; gap: .5em;