/* kit/fluido.jsx — o estilo fluido (lib/estudio/fluido.js), quadro a quadro.
 *
 * Uma peça só desenha a partitura inteira. Antes do primeiro quadro as fontes
 * carregam e cada palavra é MEDIDA (canvas): é daí que sai o tamanho da
 * pílula, do anel e do cartão. Depois, tudo é função do tempo:
 *
 *   o PORTADOR  uma forma que vive o vídeo todo e muda de lugar, tamanho,
 *               raio e preenchimento entre as batidas (ponto → pílula → anel
 *               → linha → cartão → marca). É ele que tira o corte.
 *   a PALAVRA   entra no instante em que a voz a diz (partitura: w.em), sobe
 *               meio corpo e sai do desfoque, na curva editorial. Uma por
 *               frase em serifa itálica, na cor de acento.
 *   o CARTÃO    a interface de produto que flutua do outro lado do texto, em
 *               perspectiva: terminal, chat ou painel — de forma e texto.
 *   o FUNDO     gradiente grande e macio; o brilho segue o portador devagar.
 *
 * Nada fica parado: a câmera avança e deriva, o portador respira, o cartão
 * flutua. Sem HUD, sem cantoneira, sem timecode, sem partícula, sem caixa alta. */
import React, { useState, useEffect, useMemo } from 'react';
import { delayRender, continueRender, Img, staticFile } from 'remotion';
import { CURVAS_KIT, alfa } from './base';

const ed = CURVAS_KIT.editorial, cam = CURVAS_KIT.camera;
const c01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
const lerp = (a, b, k) => a + (b - a) * k;
const lum = (hex) => { const n = parseInt(String(hex).slice(1), 16); return (0.299 * (n >> 16 & 255) + 0.587 * (n >> 8 & 255) + 0.114 * (n & 255)) / 255; };
const entra = (t) => t * t;
const SANS = (f) => '"' + f + '", system-ui, sans-serif';
const SERIF = (f) => '"' + f + '", Georgia, serif';
const MONO = (f) => '"' + f + '", ui-monospace, monospace';
const BRILHO4 = 'M 0 -1 C 0.12 -0.12 0.12 -0.12 1 0 C 0.12 0.12 0.12 0.12 0 1 C -0.12 0.12 -0.12 0.12 -1 0 C -0.12 -0.12 -0.12 -0.12 0 -1 Z';

// ── a medida ─────────────────────────────────────────────────────────────
function useFontes(P) {
  const [pronto, setPronto] = useState(false);
  const [alca] = useState(() => delayRender('fluido: carregar as fontes'));
  useEffect(() => {
    const txt = P.batidas.flatMap((b) => b.frases.flatMap((f) => f.palavras.map((w) => w.t))).join(' ') + ' 0123456789—$✓';
    Promise.all([300, 400, 500, 600].map((w) => document.fonts.load(w + ' 40px ' + SANS(P.fontes.texto), txt).catch(() => null))
      .concat([document.fonts.load('italic 400 40px ' + SERIF(P.fontes.serifa), txt).catch(() => null),
        document.fonts.load('400 40px ' + MONO(P.fontes.mono), txt).catch(() => null)]))
      .then(() => document.fonts.ready).then(() => setPronto(true), () => setPronto(true));
  }, []);
  useEffect(() => { if (pronto) continueRender(alca); }, [pronto]);
  return pronto;
}
let ctx = null;
function medir(t, tam, peso, serifa, P, mono) {
  if (!ctx) ctx = document.createElement('canvas').getContext('2d');
  ctx.font = serifa ? 'italic 400 ' + tam + 'px ' + SERIF(P.fontes.serifa) : peso + ' ' + tam + 'px ' + (mono ? MONO(P.fontes.mono) : SANS(P.fontes.texto));
  return ctx.measureText(t).width;
}

/* Um bloco de texto: quebra palavra a palavra (largura medida), cada linha
   centrada em cx (ou alinhada à esquerda/direita), o bloco centrado em cy. */
function bloco(P, f, { tam, peso = 400, max = 1e9, cx, cy, alinha = 'centro', cor, corAcento = null, mono = false }) {
  const esp = medir(' ', tam, peso, false, P, mono);
  const ws = f.palavras.map((w) => ({ ...w, w: medir(w.t, w.acento ? tam * 1.14 : tam, peso, w.acento, P, mono) }));
  const linhas = [[]];
  let lw = 0;
  for (const w of ws) {
    const cur = linhas[linhas.length - 1];
    if (cur.length && lw + esp + w.w > max) { linhas.push([w]); lw = w.w; } else { lw += (cur.length ? esp : 0) + w.w; cur.push(w); }
  }
  const lh = tam * 1.2;
  const larg = (l) => l.reduce((s, w) => s + w.w, 0) + esp * (l.length - 1);
  const w = Math.max(...linhas.map(larg)), h = linhas.length * lh;
  const top = cy - h / 2;
  const x0 = alinha === 'centro' ? cx - w / 2 : alinha === 'esq' ? cx : cx - w;
  return {
    f, tam, peso, cor, corAcento, esp, lh, w, h, top, cx, cy, mono, left: x0,
    linhas: linhas.map((l, i) => ({ palavras: l, y: top + i * lh, x: alinha === 'centro' ? cx - larg(l) / 2 : alinha === 'esq' ? cx : cx - larg(l) })),
  };
}

// ── o leiaute de cada batida: os blocos, o cartão e as chaves do portador ─
function leiaute(P) {
  const W = P.largura, H = P.altura, u = Math.min(W, H) / 1080, deitado = W >= H;
  const cx = W / 2, cy = H / 2;
  const pal = P.paleta, T = pal.tinta, S = pal.suave;
  /* O estado do portador. A cor cheia vai PRÉ-MULTIPLICADA (sa: quanto de
     acento, si: quanto de tinta): ponto de acento que vira pílula clara
     cruza as duas camadas, sem passar por um cinza no meio. */
  const forma = (x, y, w, h, o = {}) => {
    const solid = o.solid ?? 0, ac = o.ac ?? 0;
    return { x, y, w, h, r: o.r ?? Math.min(w, h) / 2, fill: o.fill ?? 0, sa: solid * ac, si: solid * (1 - ac), stroke: o.stroke ?? 0, op: o.op ?? 1 };
  };
  const chaves = [];   // [{a, b, s}]: de a até b o portador vai para s
  const vai = (a, b, s) => chaves.push({ a, b, s });
  const batidas = P.batidas.map((b, bi) => {
    const fr = (papel) => b.frases.filter((f) => f.papel === papel);
    const [pr] = fr('principal');
    const out = { b, blocos: [], extra: null };
    /* A COMPOSIÇÃO: com cartão, o texto vai para um lado (ou para cima, em
       pé) e o cartão para o outro; sem cartão, o centro. */
    const lado = b.lado || 'centro';
    const tx = !deitado || lado === 'centro' ? cx : lado === 'esq' ? cx - W * 0.21 : cx + W * 0.21;
    const ty = deitado || lado === 'centro' ? cy : cy - H * 0.2;
    const larg = lado === 'centro' ? W * (deitado ? 0.72 : 0.86) : deitado ? W * 0.36 : W * 0.86;
    if (b.ui) {
      const cw = deitado ? W * 0.34 : W * 0.8, ch = cw * (b.ui.tipo === 'terminal' ? 0.6 : 0.7);
      out.cartao = { x: deitado ? (lado === 'esq' ? cx + W * 0.2 : cx - W * 0.2) : cx, y: deitado ? cy : cy + H * 0.15, w: cw, h: ch, gira: deitado ? (lado === 'esq' ? -14 : 14) : 0 };
    }
    let ent = b.transicao === 'corte' ? [b.de, b.de] : [Math.max(0, b.de - 0.22), b.de + 0.45];
    // A primeira batida (sem ser abertura) nasce de um ponto, que então vira a forma dela.
    if (bi === 0 && b.tipo !== 'abertura') {
      vai(0, 0.01, forma(tx, ty, 0, 0, { solid: 1, ac: 1, op: 0 }));
      vai(0.02, 0.3, forma(tx, ty, 16 * u, 16 * u, { solid: 1, ac: 1 }));
      ent = [0.3, 0.8];
    }
    const poe = (bl) => { out.blocos.push(bl); return bl; };
    /* A IMAGEM (fluido.js escolhe a batida e o jeito): o texto se arruma em
       volta dela, e o portador vira um ponto discreto enquanto a foto manda. */
    if (b.imagem && pr) {
      const j = b.imagem.jeito;
      const t0 = Math.max(b.de + 0.05, Math.min(...b.frases.map((f) => f.palavras[0].em)));
      const [sb] = fr('sub');
      const branco = '#ffffff';
      let foto, tY;
      if (j === 'cartao') {
        const cw = deitado ? W * 0.36 : W * 0.74, ch = deitado ? H * 0.62 : H * 0.42;
        foto = { x: deitado ? cx + W * 0.2 : cx, y: deitado ? cy : cy + H * 0.12, w: cw, h: ch };
        tY = deitado ? cy : cy - H * 0.24;
      } else if (j === 'faixa') {
        foto = { x: cx, y: deitado ? cy - H * 0.08 : cy - H * 0.06, w: W, h: deitado ? H * 0.46 : H * 0.34 };
        tY = foto.y + foto.h / 2 + 130 * u;
      } else { foto = { x: cx, y: cy, w: W, h: H }; tY = j === 'vazado' ? cy : cy + H * (deitado ? 0.22 : 0.2); }
      const tx2 = j === 'cartao' && deitado ? cx - W * 0.2 : cx;
      const sobreFoto = j === 'janela' || j === 'circulo';
      let tam = (j === 'vazado' ? 200 : sobreFoto ? 92 : 76) * u;
      if (j === 'vazado') {
        const l100 = pr.palavras.reduce((s2, w) => s2 + medir(w.t, 100, 700, false, P), 0) + medir(' ', 100, 700, false, P) * (pr.palavras.length - 1);
        tam = Math.min(360 * u, (W * 0.84 / (1 + 0.022 * (bi + 1))) / Math.max(1, l100) * 100);
      }
      const larg2 = j === 'cartao' && deitado ? W * 0.34 : W * 0.84;
      const tb = poe(bloco(P, pr, { tam, peso: j === 'vazado' ? 700 : 400, max: larg2, cx: tx2, cy: tY, cor: sobreFoto ? branco : T, corAcento: sobreFoto ? branco : null }));
      if (sb) poe(bloco(P, sb, { tam: 34 * u, max: larg2, cx: tx2, cy: tY + tb.h / 2 + 56 * u, cor: sobreFoto ? alfa(branco, 0.82) : S }));
      out.imagem = { jeito: j, src: b.imagem.src, foto, t0, texto: tb };
      // O portador: no círculo ele É a foto (cresce); nos outros, vira um ponto e some.
      if (j === 'circulo') vai(Math.max(0, t0 - 0.5), t0 + 0.2, forma(cx, cy, 0, 0, { solid: 1, ac: 1, op: 0 }));
      else vai(ent[0], ent[1], forma(tx2, tY - tb.h / 2 - 60 * u, 10 * u, 10 * u, { solid: 1, ac: 1, op: j === 'vazado' ? 1 : 0 }));
      return out;
    }
    /* A PALAVRA GIGANTE (fluido.js marca): a frase curta enche a largura,
       sem pílula, peso fino. Na VIRADA o portador cresce até cobrir a tela
       de acento, e o texto troca para a cor que lê sobre ele. */
    if (b.gigante && pr) {
      const base = 100;
      const larguraA100 = pr.palavras.reduce((s2, w) => s2 + medir(w.t, w.acento ? base * 1.14 : base, 300, w.acento, P), 0) + medir(' ', base, 300, false, P) * (pr.palavras.length - 1);
      // Cabe já descontando o zoom que a câmera terá nesta batida (1 + 0,022 por batida) e a deriva.
      const zoomAqui = 1 + 0.022 * (bi + 1);
      const tam = Math.min(330 * u, Math.max(96 * u, (W * 0.8 / zoomAqui) / Math.max(1, larguraA100) * base));
      const sobre = b.virada ? (lum(pal.acentos[0]) > 0.55 ? pal.fundo[0] : '#ffffff') : T;
      const acentoSobre = b.virada ? sobre : pal.acentos[0];
      const g = poe(bloco(P, pr, { tam, peso: 300, max: W * 0.84 / zoomAqui, cx, cy, cor: sobre, corAcento: acentoSobre }));
      const [sb] = fr('sub');
      if (sb) poe(bloco(P, sb, { tam: 34 * u, max: W * 0.7, cx, cy: cy + g.h / 2 + 70 * u, cor: b.virada ? sobre : S }));
      // A virada começa com a PRIMEIRA frase que aparece (a voz pode dizer o sub antes do título).
      const t0 = Math.max(b.de + 0.1, Math.min(...b.frases.map((f) => f.palavras[0].em)));
      if (b.virada) {
        const D = Math.hypot(W, H) * 1.08;
        vai(Math.max(0, t0 - 0.55), t0 - 0.05, forma(cx, cy, D, D, { solid: 1, ac: 1 }));
      } else vai(ent[0], ent[1], forma(cx, cy - g.h / 2 - 70 * u, 14 * u, 14 * u, { solid: 1, ac: 1 }));
      return out;
    }
    switch (b.tipo) {
      case 'abertura': {
        const t = pr && poe(bloco(P, pr, { tam: (lado === 'centro' ? 96 : 80) * u, max: larg, cx: tx, cy: ty, cor: T }));
        const [sb] = fr('sub');
        if (sb) poe(bloco(P, sb, { tam: 36 * u, max: larg * 0.8, cx: tx, cy: ty + (t ? t.h / 2 : 0) + 64 * u, cor: S }));
        const y = ty - (t ? t.h / 2 : 0) - 80 * u;
        vai(0, 0.01, forma(tx, y, 0, 0, { solid: 1, ac: 1, op: 0 }));
        vai(0.02, 0.45, forma(tx, y, 18 * u, 18 * u, { solid: 1, ac: 1 }));
        break;
      }
      case 'numero': {
        const [nb] = fr('numero'), [rot] = fr('rotulo');
        const n = nb && poe(bloco(P, nb, { tam: 280 * u, peso: 300, cx: tx, cy: ty - 40 * u, cor: T }));
        const d = Math.max(n ? n.w : 0, 260 * u) + 140 * u;
        if (rot) poe(bloco(P, rot, { tam: 40 * u, max: larg, cx: tx, cy: ty - 40 * u + d / 2 + 64 * u, cor: S }));
        vai(ent[0], ent[1], forma(tx, ty - 40 * u, d, d, { stroke: 0.55 }));
        break;
      }
      case 'lista': {
        const its = fr('item');
        const tt = pr && poe(bloco(P, pr, { tam: 60 * u, max: W * 0.8, cx, cy: cy - (deitado ? 200 : 380) * u, cor: T }));
        const tam = 42 * u, padX = 36 * u, ph = tam * 2.2, ic = 30 * u;
        const ws = its.map((f) => medir(f.palavras.map((w) => w.t).join(' '), tam, 400, false, P) + padX * 2 + ic + 14 * u);
        const gap = 70 * u;
        const total = ws.reduce((s, w) => s + w, 0) + gap * (ws.length - 1);
        const linha = deitado && total < W * 0.94;
        let x = cx - total / 2;
        const pilulas = its.map((f, j) => {
          let pcx, pcy;
          if (linha) { pcx = x + ws[j] / 2; pcy = cy + 30 * u + (j % 2 ? 90 : -10) * u; x += ws[j] + gap; }
          else { pcx = cx + (j % 2 ? 1 : -1) * Math.min(W * 0.14, 150 * u); pcy = cy - 160 * u + j * (ph + 90 * u); }
          const bl = bloco(P, f, { tam, cx: pcx + (ic + 14 * u) / 2, cy: pcy, cor: T });
          return { f, x: pcx, y: pcy, w: ws[j], h: ph, bl, ic };
        });
        out.blocos.push(...pilulas.map((p) => p.bl));
        out.extra = { pilulas, tt };
        // O portador vira a PRIMEIRA pílula; as outras nascem na ponta da curva.
        const p0 = pilulas[0];
        // A primeira pílula nasce com o primeiro item (antes, ficava vazia e enorme esperando a voz).
        const i0 = p0 ? p0.f.palavras[0].em : b.de;
        if (p0) vai(Math.max(ent[0], i0 - 0.4), Math.max(ent[1], i0 + 0.1), forma(p0.x, p0.y, p0.w, p0.h, { fill: 1, stroke: 0.35 }));
        else vai(ent[0], ent[1], forma(cx, cy, 12 * u, 12 * u, { solid: 1, ac: 1 }));
        break;
      }
      case 'passos': {
        const its = fr('item');
        const tam = 68 * u;
        const tY = deitado ? cy : cy - 420 * u;
        const tt = pr && poe(bloco(P, pr, { tam, max: W * 0.34, cx: deitado ? cx - 64 * u : cx, cy: tY, alinha: deitado ? 'dir' : 'centro', cor: T }));
        const roda = its.map((f) => bloco(P, f, { tam, cx: deitado ? cx + 64 * u : cx, cy: deitado ? cy : cy + 60 * u, alinha: deitado ? 'esq' : 'centro', cor: T }));
        out.extra = { roda, tt, tam };
        vai(ent[0], ent[1], forma(cx, deitado ? cy : cy - 260 * u, 14 * u, 14 * u, { solid: 1, ac: 1 }));
        break;
      }
      case 'contraste': {
        const [nao] = fr('nao'), [sim] = fr('sim');
        const tam = 76 * u;
        const nb = nao && poe(bloco(P, nao, { tam: tam * 0.8, max: larg, cx, cy: cy - (deitado ? 90 : 160) * u, cor: S }));
        const sb = sim && poe(bloco(P, sim, { tam, max: larg, cx, cy: cy + (deitado ? 70 : 120) * u, cor: T }));
        if (nb) {
          const fimNao = nao.palavras[nao.palavras.length - 1].em + 0.3;
          const ly = nb.top + nb.h - nb.lh / 2;
          vai(ent[0], ent[1], forma(nb.left - 22 * u, ly, 12 * u, 12 * u, { solid: 1 }));
          vai(fimNao, fimNao + 0.4, forma(nb.left + nb.w / 2, ly, nb.w + 36 * u, 4 * u, { solid: 0.8, r: 3 }));
          out.risca = fimNao + 0.2;
        }
        if (sb) {
          const ly = sb.top + sb.h + 14 * u;
          vai(Math.max(sim.de - 0.3, (chaves[chaves.length - 1] || {}).b || 0), sim.de + 0.4, forma(sb.left + sb.w / 2, ly, sb.w, 4 * u, { solid: 1, ac: 1, r: 3 }));
        }
        break;
      }
      case 'citacao': {
        const [au] = fr('autor');
        const q = pr && bloco(P, pr, { tam: 58 * u, max: Math.min(W * 0.66, 1100 * u), cx, cy: cy - (au ? 30 * u : 0), cor: T });
        if (q) poe(q);
        const a = au && poe(bloco(P, au, { tam: 30 * u, cx, cy: (q ? q.top + q.h : cy) + 44 * u, cor: S }));
        const w = (q ? q.w : 300 * u) + 160 * u, h = (q ? q.h : 60 * u) + (a ? 80 : 20) * u + 120 * u;
        vai(ent[0], ent[1], forma(cx, cy + (a ? 10 * u : 0), w, h, { fill: 1, stroke: 0.2, r: 44 * u }));
        break;
      }
      case 'cta': {
        const [nome] = fr('marca');
        const tam = 120 * u;
        const nw = nome ? medir(b.marca.nome, tam, 600, false, P) : 0;
        const mk = 190 * u, gap = 44 * u;
        const caber = Math.min(1, (W * 0.9 - mk - gap) / Math.max(1, nw));
        const largMarca = mk + gap + nw * caber;
        const x0 = cx - largMarca / 2;
        const nb = nome && poe(bloco(P, nome, { tam: tam * caber, peso: 600, cx: x0 + mk + gap, cy: cy - 30 * u, alinha: 'esq', cor: T }));
        let y = cy + 130 * u;
        for (const f of fr('principal')) { poe(bloco(P, f, { tam: 44 * u, max: W * 0.8, cx, cy: y, cor: T })); y += 90 * u; }
        // O link vira um comando numa pílula, em mono.
        for (const f of fr('sub')) out.comando = poe(bloco(P, f, { tam: 32 * u, cx, cy: y, cor: T, mono: true }));
        // A marca se monta de três formas: o portador (círculo cheio), um anel e um ponto.
        const mx = x0 + mk / 2, my = cy - 30 * u, q = mk / 190;
        vai(ent[0], ent[1], forma(cx, cy, 80 * q, 80 * q, { solid: 1, ac: 1 }));
        vai(b.de + 0.1, b.de + 0.5, forma(mx - 42 * q, my - 22 * q, 84 * q, 84 * q, { solid: 1, ac: 1 }));
        out.extra = { mx, my, mk, nb, pecas: [
          { de: forma(cx + 420 * u, cy - 300 * u, 84 * q, 84 * q), ate: forma(mx + 44 * q, my - 22 * q, 84 * q, 84 * q), anel: true },
          { de: forma(cx - 380 * u, cy + 320 * u, 44 * q, 44 * q), ate: forma(mx + 2 * q, my + 54 * q, 44 * q, 44 * q), anel: false },
        ] };
        break;
      }
      default: {   // titulo: a pílula que envolve a frase; em duas linhas, a barra de acento ao lado
        let t = pr && bloco(P, pr, { tam: 76 * u, max: larg, cx: tx, cy: ty, cor: T });
        // Em mais de uma linha o texto alinha à esquerda, junto da barra de acento.
        if (t && t.linhas.length > 1) t = bloco(P, pr, { tam: 76 * u, max: larg, cx: tx - t.w / 2, cy: ty, alinha: 'esq', cor: T });
        if (t) poe(t);
        const [sb] = fr('sub');
        const umaLinha = !t || t.linhas.length === 1;
        const h = umaLinha ? (t ? t.tam * 2 : 60 * u) : t.h;
        if (sb) poe(umaLinha ? bloco(P, sb, { tam: 34 * u, max: larg, cx: tx, cy: ty + h / 2 + 62 * u, cor: S })
          : bloco(P, sb, { tam: 34 * u, max: larg, cx: t.left, cy: ty + h / 2 + 50 * u, alinha: 'esq', cor: S }));
        /* A pílula nasce com a primeira palavra, não antes: entrando em b.de,
           ela ficava VAZIA até a voz chegar na frase (teste de 08/10). */
        const p0 = pr ? pr.palavras[0].em : b.de;
        const entT = [Math.max(ent[0], p0 - 0.4), Math.max(ent[1], p0 + 0.1)];
        if (umaLinha) vai(entT[0], entT[1], forma(tx, ty, (t ? t.w : 200 * u) + 110 * u, h, { fill: 1, stroke: 0.35 }));
        else vai(entT[0], entT[1], forma(t.left - 40 * u, ty, 6 * u, t.h * 0.92, { solid: 1, ac: 1, r: 3 }));
      }
    }
    return out;
  });
  return { batidas, chaves, u, W, H, deitado };
}

/* O portador no tempo t: a última chave começada, saindo da anterior. */
function portadorEm(t, chaves) {
  let i = -1;
  while (i + 1 < chaves.length && chaves[i + 1].a <= t) i++;
  if (i < 0) return { ...chaves[0].s, op: 0 };
  const c = chaves[i], ant = i > 0 ? chaves[i - 1].s : { ...c.s, op: 0 };
  const k = c.b <= c.a ? 1 : cam(c01((t - c.a) / (c.b - c.a)));
  const s = {};
  for (const key of Object.keys(c.s)) s[key] = lerp(ant[key], c.s[key], k);
  return s;
}

// ── a palavra: entra quando é dita ───────────────────────────────────────
function Palavras({ bl, sai, t, P, op = 1, de = null }) {
  const pal = P.paleta;
  let n = 0;
  return bl.linhas.map((l, li) => (
    <div key={li} style={{ position: 'absolute', left: l.x, top: l.y, height: bl.lh, lineHeight: bl.lh + 'px', whiteSpace: 'nowrap' }}>
      {l.palavras.map((w, wi) => {
        const em = de != null ? de + (n++) * 0.07 : w.em;
        const k = ed(c01((t - em) / 0.5));
        const o = entra(c01((t - sai) / 0.35));
        const vis = k * (1 - o) * op;
        const mr = wi < l.palavras.length - 1 ? bl.esp : 0;
        if (vis <= 0.001) return <span key={wi} style={{ display: 'inline-block', width: w.w, marginRight: mr }} />;
        return (
          <span key={wi} style={{
            display: 'inline-block', marginRight: mr,
            opacity: vis, transform: 'translateY(' + ((1 - k) * 0.4 - o * 0.25) * bl.tam + 'px)',
            filter: (1 - k) + o > 0.02 ? 'blur(' + ((1 - k) * 8 + o * 6).toFixed(2) + 'px)' : undefined,
            fontFamily: w.acento ? SERIF(P.fontes.serifa) : bl.mono ? MONO(P.fontes.mono) : SANS(P.fontes.texto), fontStyle: w.acento ? 'italic' : 'normal',
            fontWeight: w.acento ? 400 : bl.peso, fontSize: w.acento ? bl.tam * 1.14 : bl.tam, letterSpacing: w.acento || bl.mono ? 0 : '-0.02em',
            color: w.acento ? (bl.corAcento || pal.acentos[0]) : bl.cor,
          }}>{w.t}</span>
        );
      })}
    </div>
  ));
}

const numeroTexto = (n, k) => n.antes + (n.ate * k).toLocaleString('pt-BR', { minimumFractionDigits: n.casas, maximumFractionDigits: n.casas }) + n.depois;

// ── o cartão de produto: terminal, chat ou painel, de forma e texto ─────
function Cartao({ c, ui, de: de0, sai, t, P, u: u0 }) {
  const pal = P.paleta, T = pal.tinta, A = pal.acentos[0];
  // O conteúdo escala com o cartão (em pé ele é mais largo); entra logo depois da troca.
  const u = u0 * c.w / (650 * u0), de = de0 + 0.2;
  const k = ed(c01((t - de - 0.05) / 0.6)), o = entra(c01((t - sai) / 0.35));
  const vis = k * (1 - o);
  if (vis <= 0.001) return null;
  const vidro = pal.escuro ? alfa(T, 0.06) : alfa('#ffffff', 0.78);
  const linha = alfa(T, pal.escuro ? 0.14 : 0.1);
  const pele = alfa(T, pal.escuro ? 0.2 : 0.1);
  const barra = (x, y, w, h, cor, kk = 1) => <div style={{ position: 'absolute', left: x, top: y, width: w * kk, height: h, borderRadius: h / 2, background: cor }} />;
  const pad = 34 * u, mono = { fontFamily: MONO(P.fontes.mono), fontSize: 26 * u, color: T, whiteSpace: 'nowrap', position: 'absolute' };
  let corpo = null;
  if (ui.tipo === 'terminal') {
    const linhas = ui.linhas.map((ln, j) => {
      const t0 = de + 0.55 + j * 0.5, n = Math.round(c01((t - t0) / 0.4) * ln.length);
      if (n <= 0) return null;
      const txt = ln.slice(0, n), ok = txt.startsWith('✓');
      return <div key={j} style={{ ...mono, left: pad, top: 70 * u + j * 48 * u }}>
        {ok ? <span style={{ color: A }}>✓</span> : null}{ok ? txt.slice(1) : txt}
        {j === ui.linhas.length - 1 || n < ln.length ? <span style={{ display: 'inline-block', width: 14 * u, height: 28 * u, marginLeft: 6 * u, verticalAlign: 'middle', background: A, opacity: Math.floor(t * 2.5) % 2 ? 0.2 : 0.9 }} /> : null}
      </div>;
    });
    const kk = (j) => ed(c01((t - de - 1.4 - j * 0.15) / 0.6));
    const y0 = 70 * u + ui.linhas.length * 48 * u + 16 * u;
    corpo = <>{linhas}{barra(pad, y0, c.w * 0.5, 14 * u, pele, kk(0))}{barra(pad, y0 + 30 * u, c.w * 0.34, 14 * u, pele, kk(1))}</>;
  } else if (ui.tipo === 'chat') {
    const k1 = ed(c01((t - de - 0.45) / 0.5)), k2 = ed(c01((t - de - 0.9) / 0.5)), k3 = ed(c01((t - de - 1.5) / 0.6));
    corpo = <>
      <div style={{ position: 'absolute', right: pad, top: 74 * u, maxWidth: c.w * 0.72, padding: (14 * u) + 'px ' + (22 * u) + 'px', borderRadius: 26 * u,
        background: A, color: '#ffffff', fontFamily: SANS(P.fontes.texto), fontSize: 26 * u, lineHeight: 1.3, opacity: k1, transform: 'translateY(' + (1 - k1) * 20 * u + 'px)' }}>{ui.pergunta}</div>
      <div style={{ position: 'absolute', left: pad, top: c.h * 0.55, width: c.w * 0.62, height: c.h * 0.28, borderRadius: 26 * u, background: pele, opacity: k2,
        transform: 'translateY(' + (1 - k2) * 20 * u + 'px)' }}>
        {k3 < 0.05 ? [0, 1, 2].map((j) => <div key={j} style={{ position: 'absolute', left: 24 * u + j * 22 * u, top: 24 * u, width: 12 * u, height: 12 * u, borderRadius: 99,
          background: alfa(T, 0.35 + 0.4 * (0.5 + 0.5 * Math.sin(t * 9 - j))) }} />) : null}
        {barra(24 * u, 22 * u, c.w * 0.5, 14 * u, alfa(T, 0.22), k3)}{barra(24 * u, 52 * u, c.w * 0.36, 14 * u, alfa(T, 0.22), k3)}
      </div>
    </>;
  } else {
    const bs = ui.barras || [];
    const passo = (c.w - pad * 2) / Math.max(1, bs.length), base = c.h - pad;
    corpo = <>
      <div style={{ position: 'absolute', left: pad, top: 70 * u, fontFamily: SANS(P.fontes.texto), fontSize: 24 * u, color: pal.suave, whiteSpace: 'nowrap' }}>{ui.rotulo}</div>
      <div style={{ position: 'absolute', right: pad, top: 76 * u, width: 12 * u, height: 12 * u, borderRadius: 99, background: A, boxShadow: '0 0 ' + 14 * u + 'px ' + A }} />
      {bs.map((v, j) => {
        const kb = ed(c01((t - de - 0.55 - j * 0.09) / 0.7)), h = (c.h - 170 * u) * v * kb;
        return <div key={j} style={{ position: 'absolute', left: pad + j * passo, top: base - h, width: passo * 0.62, height: h, borderRadius: 8 * u,
          background: j === bs.length - 1 ? A : pele }} />;
      })}
      {barra(pad, base + 6 * u, c.w - pad * 2, 2 * u, linha)}
    </>;
  }
  // Flutua: entra de baixo e de longe, e segue derivando (nunca para).
  const gy = Math.sin(t * 0.9) * 8 * u, gr = Math.sin(t * 0.55) * 2.5;
  return (
    <div style={{ position: 'absolute', left: c.x - c.w / 2, top: c.y - c.h / 2, width: c.w, height: c.h, borderRadius: 30 * u, overflow: 'hidden',
      background: vidro, border: (1.2 * u) + 'px solid ' + linha, backdropFilter: 'blur(' + 22 * u + 'px)', boxSizing: 'border-box',
      boxShadow: '0 ' + 40 * u + 'px ' + 80 * u + 'px -' + 30 * u + 'px ' + alfa(pal.escuro ? '#000000' : T, pal.escuro ? 0.55 : 0.28),
      opacity: vis, filter: vis < 0.98 ? 'blur(' + ((1 - vis) * 8).toFixed(2) + 'px)' : undefined,
      transform: 'perspective(' + 1600 * u + 'px) translateY(' + ((1 - k) * 90 * u + gy) + 'px) rotateY(' + (c.gira + gr) + 'deg) rotateX(' + (5 + (1 - k) * 10) + 'deg) scale(' + (0.9 + 0.1 * k) + ')' }}>
      <div style={{ position: 'absolute', left: 0, right: 0, top: 0, height: 48 * u, borderBottom: (1 * u) + 'px solid ' + linha }}>
        {[0, 1, 2].map((j) => <div key={j} style={{ position: 'absolute', left: 22 * u + j * 22 * u, top: 18 * u, width: 12 * u, height: 12 * u, borderRadius: 99, background: alfa(T, 0.22) }} />)}
      </div>
      {corpo}
    </div>
  );
}

// ── a foto: cinco jeitos de entrar, nenhum repetido no vídeo ────────────
function Foto({ im, b, sai, t, P, u, W, H }) {
  const k = ed(c01((t - (im.t0 - 0.45)) / 0.75));            // a entrada
  const o = entra(c01((t - sai) / 0.4));                      // a saída
  if (k <= 0.001 || o >= 0.999) return null;
  const vida = c01((t - im.t0) / Math.max(1, b.ate - im.t0)); // o empurrão lento enquanto ela fica
  const src = staticFile(im.src);
  const f = im.foto;
  const img = (extra = {}) => <Img src={src} style={{ position: 'absolute', left: 0, top: 0, width: '100%', height: '100%', objectFit: 'cover', ...extra }} />;
  const escurece = <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg, rgba(0,0,0,0.05) 30%, rgba(0,0,0,0.62) 100%)' }} />;
  if (im.jeito === 'janela') {
    return <div style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, overflow: 'hidden', opacity: 1 - o,
      clipPath: 'inset(0 ' + ((1 - k) * 100).toFixed(2) + '% 0 0)' }}>
      {img({ transform: 'scale(' + (1.14 - 0.08 * vida) + ') translateX(' + ((1 - k) * -6) + '%)' })}{escurece}</div>;
  }
  if (im.jeito === 'circulo') {
    const r = Math.hypot(W, H) * 0.55 * k * (1 - o);
    return <div style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, overflow: 'hidden',
      clipPath: 'circle(' + r.toFixed(1) + 'px at ' + W / 2 + 'px ' + H / 2 + 'px)' }}>
      {img({ transform: 'scale(' + (1.2 - 0.12 * k - 0.05 * vida) + ')' })}{escurece}</div>;
  }
  if (im.jeito === 'cartao') {
    const gira = (1 - k) * -38 + Math.sin(t * 0.6) * 3;
    return <div style={{ position: 'absolute', left: f.x - f.w / 2, top: f.y - f.h / 2, width: f.w, height: f.h, borderRadius: 28 * u, overflow: 'hidden',
      opacity: k * (1 - o), boxShadow: '0 ' + 50 * u + 'px ' + 90 * u + 'px -' + 30 * u + 'px rgba(0,0,0,0.55)',
      transform: 'perspective(' + 1500 * u + 'px) translateY(' + ((1 - k) * 140 * u - o * 60 * u) + 'px) rotateY(' + gira + 'deg) rotateX(' + (6 * (1 - k)) + 'deg) scale(' + (0.86 + 0.14 * k) + ')' }}>
      {img({ transform: 'scale(' + (1.08 - 0.06 * vida) + ')' })}</div>;
  }
  if (im.jeito === 'faixa') {
    const meio = (1 - k + o) * 50;
    return <div style={{ position: 'absolute', left: 0, top: f.y - f.h / 2, width: W, height: f.h, overflow: 'hidden',
      clipPath: 'inset(' + meio.toFixed(2) + '% 0 ' + meio.toFixed(2) + '% 0)' }}>
      {img({ transform: 'scale(1.12) translateX(' + (-4 + 8 * vida) + '%)' })}</div>;
  }
  // vazado: as letras do título, com a foto por dentro (SVG: o texto é o recorte).
  const tb = im.texto;
  const id = 'vz' + b.i;
  const fam = '"' + P.fontes.texto + '", system-ui, sans-serif';
  return <svg width={W} height={H} style={{ position: 'absolute', left: 0, top: 0, opacity: (1 - o) }}>
    <defs><clipPath id={id}>{tb.linhas.map((l, li) => (
      <text key={li} x={l.x} y={l.y + tb.lh * 0.8} fontFamily={fam} fontWeight={700} fontSize={tb.tam} letterSpacing={-0.02 * tb.tam}>
        {l.palavras.map((w) => w.t).join(' ')}</text>))}</clipPath></defs>
    <g clipPath={'url(#' + id + ')'} style={{ opacity: k }}>
      <image href={src} x={-W * 0.06 * (1 - vida)} y={tb.top - tb.h * 0.6} width={W * 1.12} height={tb.h * 2.2} preserveAspectRatio="xMidYMid slice"
        transform={'translate(0 ' + ((1 - k) * 40 * u) + ')'} />
    </g>
  </svg>;
}

// ── a peça ───────────────────────────────────────────────────────────────
export function Fluido({ el, t }) {
  const P = el.partitura;
  const pronto = useFontes(P);
  const L = useMemo(() => (pronto ? leiaute(P) : null), [pronto]);
  const pal = P.paleta, W = P.largura, H = P.altura;
  const [f0, f1, f2] = pal.fundo;
  const fundo = 'linear-gradient(158deg, ' + f0 + ' 0%, ' + f1 + ' 58%, ' + f2 + ' 100%)';
  if (!L) return <div style={{ position: 'absolute', inset: 0, background: fundo }} />;
  const { u } = L;
  const s = portadorEm(t, L.chaves);
  const atras = portadorEm(Math.max(0, t - 0.9), L.chaves);
  // O brilho difuso segue o portador com atraso; uma segunda mancha deriva devagar.
  const gx = (atras.x / W) * 100, gy = (atras.y / H) * 100;
  const dx = 70 + Math.sin(t * 0.21) * 14, dy = 20 + Math.cos(t * 0.17) * 12;
  const brilho = alfa(pal.acentos[1], pal.escuro ? 0.34 : 0.28);
  const sup = pal.escuro ? alfa(pal.tinta, 0.07) : alfa('#ffffff', 0.62);
  /* A câmera nunca para: em cada batida ela avança um pouco (e só avança —
     a soma é contínua na troca), deriva devagar e puxa de leve para o portador. */
  let bi = 0;
  while (bi < P.batidas.length - 1 && t >= P.batidas[bi + 1].de) bi++;
  const bt = P.batidas[bi];
  const z = 1 + 0.022 * (bi + cam(c01((t - bt.de) / Math.max(0.5, bt.ate - bt.de))));
  const px = (W / 2 - s.x) * 0.04 + Math.sin(t * 0.47) * 10 * u, py = (H / 2 - s.y) * 0.04 + Math.cos(t * 0.39) * 7 * u;
  // O portador respira.
  const resp = 1 + 0.018 * Math.sin(t * 2.1);
  const ult = P.batidas[P.batidas.length - 1];
  // Com logo, o portador some quando o logo assume a marca.
  const so = P.logo && ult.tipo === 'cta' ? 1 - ed(c01((t - ult.de - 0.65) / 0.5)) : 1;

  const camadas = [];
  L.batidas.forEach(({ b, blocos, extra, risca, cartao, comando, imagem }, i) => {
    const corte = b.transicao === 'corte' || (L.batidas[i + 1] && L.batidas[i + 1].b.transicao === 'corte');
    const ultima = i === L.batidas.length - 1;
    const sai = ultima ? 1e9 : b.ate - (corte ? 0 : 0.15), fimVis = b.ate + (corte ? 0 : 0.4);
    if (t < b.de - 0.3 || t > fimVis || (corte && t >= b.ate && !ultima)) return;
    if (cartao) camadas.push(<Cartao key={'k' + i} c={cartao} ui={b.ui} de={b.de} sai={sai} t={t} P={P} u={u} />);
    if (imagem) camadas.push(<Foto key={'f' + i} im={imagem} b={b} sai={sai} t={t} P={P} u={u} W={W} H={H} />);
    // A lista: as curvas que se desenham de uma pílula à próxima, e as pílulas que nascem na ponta.
    if (b.tipo === 'lista' && extra) {
      const ps = extra.pilulas;
      const o = 1 - entra(c01((t - sai) / 0.35));
      ps.forEach((p, j) => {
        if (j > 0) {
          const a = ps[j - 1], c = p;
          const x0 = a.x + a.w / 2, y0 = a.y, x1 = c.x - c.w / 2, y1 = c.y;
          const vert = Math.abs(x1 - x0) < 60 * u || x1 < x0;
          const d = vert
            ? 'M ' + a.x + ' ' + (a.y + a.h / 2) + ' C ' + a.x + ' ' + (a.y + a.h / 2 + 60 * u) + ' ' + c.x + ' ' + (c.y - c.h / 2 - 60 * u) + ' ' + c.x + ' ' + (c.y - c.h / 2)
            : 'M ' + x0 + ' ' + y0 + ' C ' + (x0 + (x1 - x0) * 0.6) + ' ' + y0 + ' ' + (x1 - (x1 - x0) * 0.6) + ' ' + y1 + ' ' + x1 + ' ' + y1;
          const k = ed(c01((t - (p.f.de - 0.4)) / 0.45));
          camadas.push(<svg key={'c' + i + j} width={W} height={H} style={{ position: 'absolute', left: 0, top: 0, overflow: 'visible', opacity: o }}>
            <path d={d} fill="none" stroke={alfa(pal.tinta, 0.5)} strokeWidth={2 * u} pathLength={1} strokeDasharray="1 1" strokeDashoffset={1 - k} />
          </svg>);
          const kp = ed(c01((t - (p.f.de - 0.1)) / 0.45));
          if (kp > 0) camadas.push(<div key={'p' + i + j} style={{ position: 'absolute', left: p.x - p.w / 2, top: p.y - p.h / 2, width: p.w, height: p.h, borderRadius: p.h / 2,
            background: sup, border: (1.2 * u) + 'px solid ' + alfa(pal.tinta, 0.2), opacity: kp * o, transform: 'scale(' + (0.7 + 0.3 * kp) + ')',
            boxShadow: pal.escuro ? undefined : '0 14px 34px -14px ' + alfa(pal.tinta, 0.3) }} />);
        }
        /* O marcador de cada pílula muda de vídeo para vídeo (P.marcador): o
           brilho de quatro pontas em toda lista era vício (08/10). */
        const kd = ed(c01((t - p.f.de) / 0.5));
        const mk = P.marcador || 'brilho';
        const mx0 = p.x - p.w / 2 + 34 * u, my0 = p.y - p.ic / 2;
        if (mk === 'brilho') camadas.push(<svg key={'d' + i + j} width={p.ic} height={p.ic} viewBox="-1 -1 2 2" style={{ position: 'absolute', left: mx0, top: my0,
          opacity: kd * o, transform: 'rotate(' + ((1 - kd) * -90 + Math.sin(t * 1.3 + j) * 6) + 'deg) scale(' + kd + ')' }}>
          <path d={BRILHO4} fill={pal.acentos[0]} />
        </svg>);
        else if (mk === 'numero') camadas.push(<div key={'d' + i + j} style={{ position: 'absolute', left: mx0 - 4 * u, top: p.y - p.ic * 0.62, height: p.ic * 1.24, lineHeight: p.ic * 1.24 + 'px',
          fontFamily: MONO(P.fontes.mono), fontSize: p.ic * 0.78, color: pal.acentos[0], opacity: kd * o, transform: 'translateY(' + (1 - kd) * 10 * u + 'px)' }}>{String(j + 1).padStart(2, '0')}</div>);
        else if (mk === 'traco') camadas.push(<div key={'d' + i + j} style={{ position: 'absolute', left: mx0, top: p.y - 1.5 * u, width: p.ic * kd, height: 3 * u, borderRadius: 3,
          background: pal.acentos[0], opacity: o }} />);
        else if (mk === 'ponto') camadas.push(<div key={'d' + i + j} style={{ position: 'absolute', left: mx0 + p.ic * 0.3, top: p.y - p.ic * 0.2, width: p.ic * 0.4, height: p.ic * 0.4, borderRadius: 99,
          background: pal.acentos[0], opacity: o, transform: 'scale(' + kd + ')' }} />);
      });
    }
    for (const bl of blocos) {
      const f = bl.f;
      if (b.tipo === 'passos' && extra) continue;
      if (imagem && imagem.jeito === 'vazado' && bl === imagem.texto) continue;   // as letras são a foto (Foto desenha)
      const op = f.papel === 'nao' && risca ? 1 - 0.55 * ed(c01((t - risca) / 0.4)) : 1;
      if (f.papel === 'numero' && b.numero) {
        const k = ed(c01((t - f.de) / 1.1));
        const vis = ed(c01((t - f.de) / 0.45)) * (1 - entra(c01((t - sai) / 0.35)));
        camadas.push(<div key={'n' + i} style={{ position: 'absolute', left: bl.cx - W / 2, width: W, top: bl.top, lineHeight: bl.lh + 'px', textAlign: 'center',
          fontFamily: SANS(P.fontes.texto), fontWeight: 300, fontSize: bl.tam, letterSpacing: '-0.05em', color: pal.tinta, opacity: vis,
          transform: 'translateY(' + (1 - vis) * 40 * u + 'px)', filter: vis < 0.98 ? 'blur(' + ((1 - vis) * 10).toFixed(2) + 'px)' : undefined,
          fontVariantNumeric: 'tabular-nums' }}>{numeroTexto(b.numero, k)}</div>);
        continue;
      }
      if (bl === comando) {
        // O comando numa pílula própria, que acende quando ele entra.
        const kc = ed(c01((t - f.de + 0.1) / 0.45));
        camadas.push(<div key={'cp' + i} style={{ position: 'absolute', left: bl.left - 30 * u, top: bl.top - 14 * u, width: bl.w + 60 * u, height: bl.h + 28 * u,
          borderRadius: 999, background: sup, border: (1.2 * u) + 'px solid ' + alfa(pal.tinta, 0.2), opacity: kc, transform: 'scale(' + (0.85 + 0.15 * kc) + ')' }} />);
      }
      camadas.push(<React.Fragment key={'t' + i + f.id}><Palavras bl={bl} sai={sai} t={t} P={P} op={op} /></React.Fragment>);
    }
    // A roda dos passos: o item ativo nítido, os outros girando para trás, transparentes e desfocados.
    if (b.tipo === 'passos' && extra) {
      if (extra.tt) camadas.push(<React.Fragment key={'pt' + i}><Palavras bl={extra.tt} sai={sai} t={t} P={P} /></React.Fragment>);
      const its = extra.roda;
      let at = 0;
      its.forEach((bl, j) => { if (j > 0) at += cam(c01((t - (bl.f.de - 0.25)) / 0.45)); });
      const o = (1 - entra(c01((t - sai) / 0.35))) * ed(c01((t - (its[0] ? its[0].f.de - 0.2 : b.de)) / 0.5));
      its.forEach((bl, j) => {
        const off = j - at, a = Math.abs(off);
        if (a > 3.2) return;
        const vis = Math.max(0, 1 - a * 0.3) * o;
        const k = ed(c01((t - bl.f.de) / 0.5));
        const op = Math.max(vis * (0.4 + 0.6 * c01(1 - a)), a < 0.5 ? vis * k : 0);
        camadas.push(<div key={'r' + i + j} style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, opacity: op,
          transform: 'perspective(' + 900 * u + 'px) translateY(' + Math.sin(off * 0.42) * extra.tam * 3.6 + 'px) translateZ(' + (-(1 - Math.cos(off * 0.42)) * 300 * u) + 'px) rotateX(' + (-off * 24) + 'deg)',
          transformOrigin: '50% ' + bl.cy + 'px', filter: a > 0.05 ? 'blur(' + (a * 3.5).toFixed(2) + 'px)' : undefined }}>
          <Palavras bl={bl} de={-10} sai={1e9} t={t} P={P} />
        </div>);
      });
    }
    // A marca: um anel e um ponto chegam e travam no portador; com logo, as formas viram o logo.
    if (b.tipo === 'cta' && extra) {
      const trava = b.de + 0.5;
      const kl = P.logo ? ed(c01((t - trava - 0.15) / 0.5)) : 0;
      extra.pecas.forEach((pc, j) => {
        const k = cam(c01((t - (b.de - 0.1 + j * 0.08)) / 0.55));
        const q = {}; for (const key of Object.keys(pc.ate)) q[key] = lerp(pc.de[key], pc.ate[key], k);
        const ent = c01((t - b.de + 0.2) / 0.3) * (1 - kl);
        camadas.push(<div key={'m' + i + j} style={{ position: 'absolute', left: q.x - q.w / 2, top: q.y - q.h / 2, width: q.w, height: q.h, borderRadius: 999,
          background: pc.anel ? 'transparent' : pal.tinta, border: pc.anel ? (8 * u) + 'px solid ' + pal.tinta : undefined, opacity: ent, boxSizing: 'border-box' }} />);
      });
      // O pulso de quando trava (o impacto do som).
      const kp = c01((t - trava) / 0.7);
      if (kp > 0 && kp < 1) {
        const r = extra.mk * (0.5 + kp * 0.6);
        camadas.push(<div key={'pu' + i} style={{ position: 'absolute', left: extra.mx - r, top: extra.my - r, width: r * 2, height: r * 2, borderRadius: 999,
          border: (2 * u) + 'px solid ' + alfa(pal.acentos[0], 0.6 * (1 - kp)) }} />);
      }
      if (P.logo && kl > 0) camadas.push(<div key={'lg' + i} style={{ position: 'absolute', left: extra.mx - extra.mk / 2, top: extra.my - extra.mk / 2, width: extra.mk, height: extra.mk,
        opacity: kl, transform: 'scale(' + (0.8 + 0.2 * kl) + ')' }}><Img src={staticFile(P.logo)} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /></div>);
    }
  });

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'hidden', background: fundo }}>
      <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 70% 60% at ' + gx + '% ' + gy + '%, ' + brilho + ' 0%, transparent 70%)' }} />
      <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 80% 70% at ' + dx + '% ' + dy + '%, ' + alfa(f2, pal.escuro ? 0.45 : 0.5) + ' 0%, transparent 65%)' }} />
      <div style={{ position: 'absolute', inset: 0, transform: 'translate(' + px + 'px,' + py + 'px) scale(' + z + ')', transformOrigin: '50% 50%' }}>
        <div style={{ position: 'absolute', left: s.x - s.w * resp / 2, top: s.y - s.h * resp / 2, width: Math.max(0, s.w * resp), height: Math.max(0, s.h * resp), borderRadius: s.r,
          background: pal.escuro ? alfa(pal.tinta, 0.07 * s.fill) : alfa('#ffffff', 0.62 * s.fill),
          border: s.stroke > 0.01 ? (1.6 * u) + 'px solid ' + alfa(pal.tinta, s.stroke * (pal.escuro ? 0.6 : 0.55)) : undefined,
          boxShadow: !pal.escuro && s.fill > 0.5 && s.h > 30 * u ? '0 20px 50px -24px ' + alfa(pal.tinta, 0.35 * s.fill) : undefined,
          opacity: s.op * so, boxSizing: 'border-box' }}>
          <div style={{ position: 'absolute', inset: 0, borderRadius: s.r, background: alfa(pal.tinta, s.si) }} />
          <div style={{ position: 'absolute', inset: 0, borderRadius: s.r, background: alfa(pal.acentos[0], s.sa),
            boxShadow: s.sa > 0.05 && s.h < 100 * u ? '0 0 ' + 30 * u + 'px ' + alfa(pal.acentos[0], 0.55 * s.sa) : undefined }} />
        </div>
        {camadas}
      </div>
    </div>
  );
}

export const FLUIDO = { fluido: Fluido };
