/*  cenas.jsx — os 8 tipos de cena do estudio_motion, montados com o base.jsx (ES3).

    O Node (maquina.js) entrega um PLANO em JSON; `montar(PLANO)` cria a comp MAIN, uma comp por cena e as
    trocas. Cada cena tem a sua câmera (que nunca para) e os seus sons no tempo.

    PLANO = { w, h, fps, fontes:{titulo, texto, destaque}, trocas:['encolhe'|'empurra'|'mascara'],
              cenas:[{ tipo, dur, pal:{bg, fg, ac, mudo, card, brilho}, ...dados do tipo }] }
*/

function kVert(comp) { return comp.height >= comp.width * 0.95; }   // o quadrado 1:1 também empilha
function kU(comp) { return Math.min(comp.width, comp.height) / 1080; }
function kFillProp(L) { return L.property('ADBE Root Vectors Group').property(1).property('ADBE Vectors Group').property('ADBE Vector Graphic - Fill').property('ADBE Vector Fill Color'); }
function kBase(comp, P) {
    fundo(comp, P.bg, { brilho: P.brilho });
    var cam = camera(comp);
    // a câmera (CAM) leva dois filhos: o título (direto nela) e o CONTEUDO, que desce e encolhe para caber abaixo da faixa reservada do título
    var cont = comp.layers.addNull(comp.duration); cont.name = 'CONTEUDO'; cont.enabled = false; kPai(cont, cam.no);
    kA(cont).setValue([comp.width / 2, comp.height / 2]); kP(cont).setValue([comp.width / 2, comp.height / 2]);
    cam.camNo = cam.no; cam.no = cont; KIT.remap = null;
    if (KIT.elementos.length && KIT.ambiente !== false && !KIT.semAmbiente) KIT.ambienteFn = function (cmp, cm) { kAmbiente(cmp, KIT.pal, cm); };
    else KIT.ambienteFn = null;
    return cam;
}
/* Os objetos do assunto, quando a cena não tem elemento próprio: 1 HERÓI (≥ 45% da largura útil) e 1 apoio (≥ 12% da largura, mais apagado),
   postos NO FIM da cena no primeiro canto livre (sem tocar em texto nem em elemento já registrados; dentro dos 8% de margem). Sem espaço, sem enfeite. */
function kAmbiente(comp, P, cam) {
    if (KIT.cenaComElemento || !cam) return;
    var W = comp.width, H = comp.height, m = Math.min(W, H), ze = kZonaTexto(comp, false), mx = ze.x, my = ze.top, myb = ze.bot, k, colocados = [], R = [], ri;
    for (ri = 0; ri < KIT.rects.length; ri++) if (KIT.rects[ri].cena == KIT.cenaIdx && !KIT.rects[ri].fora) R.push(kRectLayout(KIT.rects[ri]));
    var tams = [m * 0.5, m * 0.15];
    // folga do texto: 7% da largura (no mínimo 60 px), pra a câmera que empurra (até 1,1×) não encostar o objeto nas letras
    var fo = Math.max(60, W * 0.07);
    function livre(x, y, w) {
        var x0 = x - w / 2 - fo, x1 = x + w / 2 + fo, y0 = y - w / 2 - fo, y1 = y + w / 2 + fo, i;
        if (x - w / 2 < mx + W * 0.04 || x + w / 2 > W - mx - W * 0.04 || y - w / 2 < my + W * 0.04 || y + w / 2 > H - myb - W * 0.04) return false;
        for (i = 0; i < R.length; i++) { var r = R[i]; if (x0 < r.x1 && x1 > r.x0 && y0 < r.y1 && y1 > r.y0) return false; }
        for (i = 0; i < colocados.length; i++) { var q = colocados[i]; if (x0 < q[2] && x1 > q[0] && y0 < q[3] && y1 > q[1]) return false; }
        return true;
    }
    for (k = 0; k < 2; k++) {
        var w = tams[k], im = W * 0.07, xs = [mx + im + w / 2, W - mx - im - w / 2, W / 2], ys = [my + im + w / 2, H - myb - im - w / 2, H / 2], cands = [], a, b;
        for (a = 0; a < 3; a++) for (b = 0; b < 3; b++) if (!(a == 2 && b == 2)) cands.push([xs[a], ys[b]]);
        var ordem = (KIT.cenaIdx + k) % cands.length, n;
        for (n = 0; n < cands.length; n++) {
            var cd = cands[(ordem + n) % cands.length];
            if (!livre(cd[0], cd[1], w)) continue;
            var e = elemento(comp, KIT.cenaIdx + k * 2, { x: cd[0], y: cd[1], w: w, parent: cam.camNo || cam.no, flutua: 14, fase: k * 2.1 + KIT.cenaIdx, rot: k ? -12 : 10, entrada: 'pop', t0: 0.12 + k * 0.12, quique: 0.88, nome: 'ambiente' });
            colocados.push([cd[0] - w / 2 - 60, cd[1] - w / 2 - 60, cd[0] + w / 2 + 60, cd[1] + w / 2 + 60]);
            break;
        }
    }
}
// onde o raio que sai de (cx, cy) no ângulo a cruza a borda da caixa (+ m para fora); sem caixa, o próprio centro
function kNaBorda(c, cx, cy, a, m) {
    if (!c) return [cx, cy];
    var dx = Math.cos(a), dy = Math.sin(a), hw = (c[2] - c[0]) / 2, hh = (c[3] - c[1]) / 2;
    var k = Math.min(Math.abs(dx) > 1e-6 ? hw / Math.abs(dx) : 1e9, Math.abs(dy) > 1e-6 ? hh / Math.abs(dy) : 1e9) + (m || 0);
    return [cx + dx * k, cy + dy * k];
}
// pontos que estalam em volta de um centro (confete discreto), com som
/* caixa (opcional, [x0, y0, x1, y1]): o estouro nasce na BORDA dela e vai para fora — o confete nunca passa por cima do texto que
   mora dentro (o rótulo do botão virava "Começa: agora" com um ponto branco em cima do "r"). */
function kEstouro(comp, cam, cx, cy, t, cor, n, raio, som0, caixa) {
    var i;
    for (i = 0; i < n; i++) {
        var a = (i / n) * Math.PI * 2 + 0.4, r = raio * (0.8 + 0.4 * ((i * 37) % 10) / 10), d = 10 + (i % 3) * 6;
        var p0 = kNaBorda(caixa, cx, cy, a, d + 6), p1 = [p0[0] + Math.cos(a) * r, p0[1] + Math.sin(a) * r];
        var L = forma(comp, 'circulo', { x: p0[0], y: p0[1], w: d * 2, cor: cor, parent: cam.no, nome: 'estouro', alfa: 0 });
        curva(kP(L), [[t, p0, null, 20], [t + 0.7, p1, 85, null]]);
        curva(kO(L), [[t - 0.04, 0, null, 30], [t, 100, null, 30], [t + 0.2, 100, 50, 30], [t + 0.75, 0, 70, null]]);
        curva(kS(L), [[t, [40, 40], null, 20], [t + 0.25, [100, 100], 70, 30], [t + 0.75, [20, 20], 70, null]]);
    }
    if (som0 !== false) som(t, 'pop', 0.8);
}

// ---------------------------------------------------------------- gancho
function cenaGancho(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.14;
    var ty = v ? H * 0.42 : H * 0.44;
    var tit = texto(comp, d.titulo || '', { tamanho: (v ? 150 : 128) * u, cor: P.fg, corDestaque: P.ac, x: W / 2, y: ty, parent: cam.no, entrada: d.entrada || 'voa', t0: T0, largura: W * (v ? 0.86 : 0.78), maxLinhas: 4 });
    var i;
    for (i = 0; i < tit.tempos.length; i++) som(tit.tempos[i], i % 2 ? 'tique' : 'pop', i ? 0.45 : 0.7);
    if (tit.camadas.length) som(tit.fim - 0.25, 'brilho', 0.5);
    var yb = ty + tit.h / 2 + 90 * u;
    if (d.sub) {
        var sb = texto(comp, d.sub, { fonte: P.fonteTexto, tamanho: 54 * u, cor: P.mudo, x: W / 2, y: yb, parent: cam.no, entrada: 'sobe', t0: tit.fim + 0.05, largura: W * 0.74, maxLinhas: 3, entreLinhas: 1.25 });
        som(tit.fim + 0.08, 'tique', 0.4);
    }
    cam.empurra(0.9, 0, -24 * u, 1.05).empurra(comp.duration - 0.3, 0, 30 * u, 1.1);
    fecha(comp);
}

// ---------------------------------------------------------------- celular
function cenaCelular(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.12;
    var esc = v ? 0.66 * H / 960 * 0.92 : 0.7 * H / 960;
    var px = v ? W / 2 : W * 0.7, py = v ? H * 0.585 : H * 0.5;
    var tit;
    if (d.titulo) {
        tit = texto(comp, d.titulo, { tamanho: (v ? 104 : 96) * u, cor: P.fg, corDestaque: P.ac, x: v ? W / 2 : W * 0.3, y: v ? H * 0.15 : H * 0.5, parent: cam.no, entrada: 'palavra', t0: T0, largura: v ? W * 0.88 : W * 0.46, maxLinhas: 3 });
        var i0; for (i0 = 0; i0 < tit.tempos.length; i0++) som(tit.tempos[i0], 'pop', 0.45);
    }
    var cel = celular(comp, { x: px, y: py, escala: esc, parent: cam.no, t0: T0 + 0.1, tela: P.telaCor || '#F6F6F8' });
    var tq = d.toques && d.toques.length ? d.toques : ['Agendar', 'Confirmar'];
    tq = tq.slice(0, 4);
    // cabeçalho da tela
    var cab = texto(comp, d.tela && d.tela.charAt && d.tela.charAt(0) != '#' && d.tela.indexOf('.') < 0 ? d.tela : (d.cabecalho || 'Hoje'), { tamanho: 54, cor: '#1B1B1F', x: 0, y: -330, parent: cel.no, entrada: 'sobe', t0: T0 + 0.7, largura: 360, maxLinhas: 1, sublinhado: false });
    var n = tq.length, y0 = -150, passo = 124, i;
    for (i = 0; i < n; i++) {
        var ti = T0 + 1.0 + i * 0.85, yy = y0 + i * passo;
        var card = forma(comp, 'retangulo', { x: 0, y: yy, w: 372, h: 96, r: 30, cor: '#FFFFFF', traco: '#E3E3E8', tracoL: 3, parent: cel.no, entrada: 'pop', t0: ti, quique: 0.55, nome: 'item' });
        var bolinha = forma(comp, 'circulo', { x: -138, y: yy, w: 44, cor: '#E3E3E8', parent: cel.no, entrada: 'pop', t0: ti + 0.04, nome: 'bolinha' });
        var tx = texto(comp, String(tq[i]), { tamanho: 34, cor: '#1B1B1F', x: 30, y: yy, parent: cel.no, entrada: 'sobe', t0: ti + 0.06, largura: 224, maxLinhas: 1, sublinhado: false });
        som(ti, 'pop', 0.55);
        var tt = ti + 0.55;
        cel.toque(tt, 100, yy, { cor: '#B7B7C0' });
        curva(kS(card), [[tt, [100, 100], null, 25], [tt + 0.08, [93, 93], 70, 40], [tt + 0.3, [100, 100], 70, null]]);
        try { curva(kFillProp(bolinha), [[tt, kCor('#E3E3E8'), null, 30], [tt + 0.15, kCor(P.ac), 60, null]]); } catch (e) { }
        check(comp, { x: -138, y: yy, tam: 24, cor: '#FFFFFF', parent: cel.no, t0: tt + 0.08 });
        som(tt + 0.12, 'ponto' + (1 + i % 5), 0.5);
    }
    cel.sai(T0 + 1.0 + n * 0.85 + 0.2);
    cam.empurra(0.9, v ? 0 : 60 * u, v ? 20 * u : 0, 1.03).empurra(comp.duration - 0.3, v ? 0 : 40 * u, v ? 60 * u : 0, v ? 1.1 : 1.12);
    fecha(comp);
}

// ---------------------------------------------------------------- número
function cenaNumero(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.14, R = 1.25;
    var val = String(d.valor == null ? '100' : d.valor);
    var m = val.match(/^([^0-9]*)([0-9][0-9.,]*)(.*)$/);
    var pre = m ? m[1] : '', num = m ? m[2] : '0', suf = m ? m[3] : '';
    var alvo = parseFloat(num.replace(/\./g, '').replace(',', '.'));
    if (isNaN(alvo)) alvo = 0;
    var dec = num.indexOf(',') >= 0 ? num.length - num.indexOf(',') - 1 : 0;
    var cx = W / 2, cy = v ? H * 0.42 : H * 0.44, D = Math.min(W, H) * (v ? 0.8 : 0.6);
    var anel = forma(comp, 'circulo', { x: cx, y: cy, w: D, traco: P.card, tracoL: 22 * u, parent: cam.no, entrada: 'cresce', t0: T0, nome: 'trilho' });
    var prog = forma(comp, 'circulo', { x: cx, y: cy, w: D, traco: P.ac, tracoL: 22 * u, parent: cam.no, nome: 'progresso' });
    kR(prog).setValue(-90);
    var g = prog.property('ADBE Root Vectors Group').property(1).property('ADBE Vectors Group');
    var tm = g.addProperty('ADBE Vector Filter - Trim');
    curva(tm.property('ADBE Vector Trim End'), [[T0 + 0.1, 0, null, 20], [T0 + 0.1 + R, 100, 80, null]]);
    try { g.property('ADBE Vector Graphic - Stroke').property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) { }
    // o número rolando
    var fmt = alvo >= 1000 || num.indexOf('.') >= 0 ? true : false;
    var final = pre + num + suf;
    var fonte = P.fonteTitulo, tam = (v ? 300 : 280) * u;
    var L = comp.layers.addText(final); L.name = 'numero'; kPai(L, cam.no);
    kDoc(L, fonte, tam, P.fg);
    var wmax = D * 0.8, w = L.sourceRectAtTime(0, false).width;
    while (w > wmax && tam > 40) { tam *= 0.9; kDoc(L, fonte, tam, P.fg); w = L.sourceRectAtTime(0, false).width; }
    kP(L).setValue([cx, cy + tam * 0.33]);
    var td = L.property('ADBE Text Properties').property('ADBE Text Document');
    var ex = 'var n = ease(time, ' + (T0 + 0.1) + ', ' + (T0 + 0.1 + R) + ', 0, ' + alvo + ');' +
        'var s = ' + (dec ? 'n.toFixed(' + dec + ').replace(".", ",")' : 'String(Math.round(n))') + ';' +
        (fmt ? 's = s.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ".");' : '') +
        'if (time < ' + (T0) + ') s = "0"; "' + pre.replace(/"/g, '') + '" + s + "' + suf.replace(/"/g, '') + '";';
    td.expression = ex;
    curva(kO(L), [[T0, 0, null, 30], [T0 + 0.2, 100, 60, null]]);
    curva(kS(L), [[T0 + 0.1 + R, [100, 100], null, 25], [T0 + 0.1 + R + 0.12, [112, 112], 70, 40], [T0 + 0.1 + R + 0.45, [100, 100], 70, null]]);
    som(T0 + 0.1, 'subida', 0.7);
    som(T0 + 0.1 + R, 'impacto', 0.8); som(T0 + 0.1 + R + 0.02, 'brilho', 0.6);
    var rot = texto(comp, d.rotulo || '', { fonte: P.fonteTexto, tamanho: 58 * u, cor: P.mudo, x: W / 2, y: v ? cy + D / 2 + 150 * u : cy + D / 2 + 85 * u, parent: cam.no, entrada: 'sobe', t0: T0 + 0.1 + R + 0.1, largura: W * 0.8, maxLinhas: 3, sublinhado: false, entreLinhas: 1.2 });
    kEstouro(comp, cam, cx, cy, T0 + 0.1 + R + 0.02, P.ac, 10, D * 0.62, false);
    cam.empurra(0.8, 0, 0, 1.0).empurra(T0 + 0.1 + R, 0, -20 * u, 1.12).empurra(comp.duration - 0.2, 0, 10, 1.14);
    fecha(comp);
}

// ---------------------------------------------------------------- lista (itens em laço: o destaque percorre e volta ao primeiro)
function cenaLista(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.12;
    var it = (d.itens && d.itens.length ? d.itens : ['Item um', 'Item dois', 'Item três']).slice(0, 5), n = it.length, i;
    var altura = Math.min((v ? 190 : 150) * u, (H * (v ? 0.62 : 0.8)) / n - 24 * u), gap = 22 * u, wc = W * (v ? 0.86 : 0.62);
    var total = n * altura + (n - 1) * gap, top = (v ? H * 0.5 : H * 0.5) - total / 2 + (d.titulo ? 70 * u : 0);
    if (d.titulo) {
        var tt = texto(comp, d.titulo, { tamanho: (v ? 88 : 78) * u, cor: P.fg, corDestaque: P.ac, x: W / 2, y: top - 130 * u, parent: cam.no, entrada: 'sobe', t0: T0, largura: W * 0.84, maxLinhas: 2 });
        som(T0, 'tique', 0.5);
    }
    var ys = [];
    for (i = 0; i < n; i++) {
        var y = top + altura / 2 + i * (altura + gap), ti = T0 + 0.3 + i * 0.32, dir = i % 2 ? 1 : -1; ys.push(y);
        var card = forma(comp, 'pilula', { x: W / 2, y: y, w: wc, h: altura, cor: P.card, parent: cam.no, nome: 'item' });
        curva(kO(card), [[ti, 0, null, 30], [ti + 0.15, 100, 60, null]]);
        mola(kP(card), ti, [W / 2 + dir * W * 0.4, y], [W / 2, y], { quique: 0.45, dur: 0.65 });
        var dot = forma(comp, 'circulo', { x: W / 2 - wc / 2 + altura / 2, y: y, w: altura * 0.56, cor: P.ac, parent: cam.no, entrada: 'pop', t0: ti + 0.12, quique: 0.7, nome: 'marca' });
        check(comp, { x: W / 2 - wc / 2 + altura / 2, y: y, tam: altura * 0.3, cor: P.bg, parent: cam.no, t0: ti + 0.22 });
        texto(comp, String(it[i]), { tamanho: Math.min(66 * u, altura * 0.4), cor: P.fg, corDestaque: P.ac, x: W / 2 + altura * 0.3, y: y, parent: cam.no, entrada: 'sobe', t0: ti + 0.1, largura: wc - altura * 1.5, maxLinhas: 2, sublinhado: false });
        som(ti + 0.05, 'ponto' + (1 + i % 5), 0.6);
    }
    // o laço: um contorno que percorre os itens e fecha o ciclo voltando ao primeiro
    var tl = T0 + 0.3 + n * 0.32 + 0.25;
    var cur = forma(comp, 'pilula', { x: W / 2, y: ys[0], w: wc + 24 * u, h: altura + 24 * u, traco: P.ac, tracoL: 8 * u, parent: cam.no, nome: 'laco', alfa: 0 });
    var ky = [[tl, [W / 2, ys[0]], null, 25]], ko = [[tl, 0, null, 30], [tl + 0.15, 100, 60, 30]];
    for (i = 1; i < n; i++) ky.push([tl + i * 0.42, [W / 2, ys[i]], 70, 25]);
    ky.push([tl + n * 0.42, [W / 2, ys[0]], 70, null]);
    curva(kP(cur), ky); curva(kO(cur), ko);
    for (i = 1; i <= n; i++) som(tl + i * 0.42 - 0.05, 'tique', 0.4);
    cam.empurra(0.8, 0, -10 * u, 1.03).empurra(comp.duration - 0.2, 0, 20 * u, 1.08);
    fecha(comp);
}

// ---------------------------------------------------------------- antes e depois
function cenaAntesDepois(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.12;
    var cw = v ? W * 0.84 : W * 0.38, ch = v ? H * 0.26 : H * 0.56;
    var ax = v ? W / 2 : W * 0.26, ay = v ? H * 0.34 : H * 0.5, bx = v ? W / 2 : W * 0.74, by = v ? H * 0.68 : H * 0.5;
    var A = forma(comp, 'retangulo', { x: ax, y: ay, w: cw, h: ch, r: 56 * u, cor: P.card, traco: P.mudo, tracoL: 4 * u, parent: cam.no, entrada: 'pop', t0: T0, quique: 0.4, nome: 'antes' });
    var la = forma(comp, 'pilula', { x: ax, y: ay - ch / 2, w: 260 * u, h: 80 * u, cor: P.mudo, parent: cam.no, entrada: 'pop', t0: T0 + 0.15, nome: 'rotulo antes' });
    texto(comp, 'ANTES', { fonte: P.fonteTitulo, tamanho: 44 * u, cor: P.bg, x: ax, y: ay - ch / 2, parent: cam.no, entrada: 'sobe', t0: T0 + 0.18, largura: 200 * u, maxLinhas: 1, sublinhado: false });
    texto(comp, d.antes || '', { fonte: P.fonteTexto, tamanho: 64 * u, cor: P.mudo, x: ax, y: ay + 20 * u, parent: cam.no, entrada: 'palavra', t0: T0 + 0.3, largura: cw * 0.82, maxLinhas: 4, sublinhado: false });
    som(T0, 'pop', 0.6); som(T0 + 0.3, 'tique', 0.4);
    // o antes treme de leve, cansado
    kP(A).expression = 'value + [Math.sin(time*18)*3, 0]';
    var t2 = T0 + 1.2;
    var seta = texto(comp, '>', { fonte: P.fonteTitulo, tamanho: 190 * u, cor: P.ac, x: v ? W / 2 : W / 2, y: v ? H * 0.5 : H * 0.5, parent: cam.no, entrada: 'palavra', t0: t2 - 0.25, sublinhado: false, largura: 200, sobrepoe: true, semAuditoria: true });
    if (v) for (var s = 0; s < seta.camadas.length; s++) kR(seta.camadas[s]).expression = 'value + 90';
    som(t2 - 0.25, 'swoosh', 0.6);
    var B = forma(comp, 'retangulo', { x: bx, y: by, w: cw, h: ch, r: 56 * u, cor: kCardAc(P), parent: cam.no, nome: 'depois' });
    var cor2 = kSobreCard(P);
    mola(kS(B), t2, [60, 60], [100, 100], { quique: 0.7, dur: 0.75 });
    curva(kO(B), [[t2, 0, null, 30], [t2 + 0.1, 100, 60, null]]);
    mola(kR(B), t2, v ? 3 : -3, 0, { quique: 0.5, dur: 0.8 });
    var lb = forma(comp, 'pilula', { x: bx, y: by - ch / 2, w: 290 * u, h: 80 * u, cor: P.fg, parent: cam.no, entrada: 'pop', t0: t2 + 0.15, nome: 'rotulo depois' });
    texto(comp, 'DEPOIS', { fonte: P.fonteTitulo, tamanho: 44 * u, cor: P.bg, x: bx, y: by - ch / 2, parent: cam.no, entrada: 'sobe', t0: t2 + 0.18, largura: 220 * u, maxLinhas: 1, sublinhado: false });
    texto(comp, d.depois || '', { tamanho: 96 * u, cor: cor2, corDestaque: cor2, x: bx, y: by + 24 * u, parent: cam.no, entrada: 'palavra', t0: t2 + 0.32, largura: cw * 0.84, maxLinhas: 4, sublinhado: false });
    som(t2, 'impacto', 0.7); som(t2 + 0.05, 'brilho', 0.6);
    kEstouro(comp, cam, bx, by, t2 + 0.05, P.fg, 8, ch * 0.4, false, [bx - cw / 2, by - ch / 2, bx + cw / 2, by + ch / 2]);
    cam.empurra(0.8, v ? 0 : -60 * u, v ? -120 * u : 0, 1.02).empurra(t2 + 0.5, v ? 0 : 70 * u, v ? 130 * u : 0, 1.1).empurra(comp.duration - 0.2, v ? 0 : 90 * u, v ? 150 * u : 0, 1.13);
    fecha(comp);
}

// ---------------------------------------------------------------- ícones que estalam em volta
function cenaIcones(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.12;
    var it = (d.itens && d.itens.length ? d.itens : ['Um', 'Dois', 'Três']).slice(0, 6), n = it.length, i;
    var cx = W / 2, cy = v ? H * 0.46 : H * 0.5, R = Math.min(W, H) * (v ? 0.28 : 0.34), bw = Math.min(W, H) * (v ? 0.2 : 0.22);
    var hub = forma(comp, 'circulo', { x: cx, y: cy, w: bw * 1.35, cor: P.ac, parent: cam.no, entrada: 'pop', t0: T0, quique: 0.55, nome: 'centro' });
    var hc = kLum(P.ac) > 0.5 ? '#14110F' : '#FFFFFF';
    if (d.titulo) texto(comp, d.titulo, { tamanho: 58 * u, cor: hc, corDestaque: hc, x: cx, y: cy, parent: cam.no, entrada: 'palavra', t0: T0 + 0.1, largura: bw * 1.1, maxLinhas: 3, sublinhado: false });
    som(T0, 'pop', 0.8);
    for (i = 0; i < n; i++) {
        var a = -Math.PI / 2 + i * Math.PI * 2 / n, ti = T0 + 0.45 + i * 0.26;
        var x = cx + Math.cos(a) * R * (v ? 1.0 : 1.35), y = cy + Math.sin(a) * R * (v ? 1.18 : 1.0);
        var lig = forma(comp, 'retangulo', { x: (cx + x) / 2, y: (cy + y) / 2, w: Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)), h: 6 * u, cor: P.mudo, parent: cam.no, nome: 'liga', alfa: 0 });
        kR(lig).setValue(Math.atan2(y - cy, x - cx) * 180 / Math.PI);
        curva(kO(lig), [[ti - 0.1, 0, null, 30], [ti + 0.1, 45, 60, null]]);
        var b = forma(comp, 'circulo', { x: x, y: y, w: bw, cor: P.card, traco: P.ac, tracoL: 7 * u, parent: cam.no, entrada: 'pop', t0: ti, quique: 0.75, nome: 'icone' });
        var ini = String(it[i]).replace(/[^A-Za-zÀ-ÿ0-9]/g, '').charAt(0).toUpperCase() || '+';
        if (KIT.elementos.length) elemento(comp, i, { x: x, y: y, w: bw * 0.62, parent: cam.no, entrada: 'pop', t0: ti + 0.05, quique: 0.7 });
        else texto(comp, ini, { fonte: P.fonteTitulo, tamanho: bw * 0.52, cor: P.ac, corDestaque: P.ac, x: x, y: y, parent: cam.no, entrada: 'palavra', t0: ti + 0.05, sublinhado: false, largura: bw });
        texto(comp, String(it[i]), { opcional: true, fonte: P.fonteTexto, tamanho: 38 * u, cor: P.fg, x: x, y: y + bw / 2 + 44 * u, parent: cam.no, entrada: 'sobe', t0: ti + 0.12, largura: Math.min(W * 0.3, 340 * u), maxLinhas: 2, sublinhado: false });
        kP(b).expression = 'value + [Math.sin(time*2.1+' + i + ')*9, Math.cos(time*1.7+' + i + ')*9]';
        som(ti, 'ponto' + (1 + i % 5), 0.7);
    }
    cam.empurra(0.9, 0, 0, 1.0).empurra(T0 + 0.45 + n * 0.26, 0, -20 * u, 1.1).empurra(comp.duration - 0.2, 0, 10 * u, 1.14);
    fecha(comp);
}

// ---------------------------------------------------------------- citação
function cenaCitacao(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.14;
    var aspas = texto(comp, '“', { fonte: P.fonteDestaque, tamanho: 520 * u, cor: P.ac, corDestaque: P.ac, x: v ? W * 0.2 : W * 0.14, y: v ? H * 0.27 : H * 0.3, parent: cam.no, entrada: 'palavra', t0: T0, sublinhado: false, largura: 600, sobrepoe: true });
    som(T0, 'pop', 0.7);
    var tx = texto(comp, d.texto || '', { tamanho: (v ? 92 : 84) * u, cor: P.fg, corDestaque: P.ac, x: W / 2, y: v ? H * 0.5 : H * 0.52, parent: cam.no, entrada: 'palavra', t0: T0 + 0.35, passo: 0.14, largura: W * (v ? 0.82 : 0.7), maxLinhas: 6 });
    var i; for (i = 0; i < tx.tempos.length; i++) som(tx.tempos[i], 'tique', 0.4);
    if (d.autor) {
        var yy = tx.y + tx.h / 2 + 90 * u;
        var barra = forma(comp, 'pilula', { x: W / 2 - 150 * u, y: yy, w: 90 * u, h: 8 * u, cor: P.ac, parent: cam.no, nome: 'barra' });
        kA(barra).setValue([-45 * u, 0]);
        curva(kS(barra), [[tx.fim, [0, 100], null, 30], [tx.fim + 0.4, [100, 100], 80, null]]);
        texto(comp, String(d.autor), { fonte: P.fonteTexto, tamanho: 46 * u, cor: P.mudo, x: W / 2 + 50 * u, y: yy, parent: cam.no, entrada: 'sobe', t0: tx.fim + 0.05, largura: W * 0.6, maxLinhas: 1, sublinhado: false });
        som(tx.fim, 'brilho', 0.5);
    }
    cam.empurra(0.9, 0, 0, 1.04).empurra(comp.duration - 0.2, 20 * u, 40 * u, 1.12);
    fecha(comp);
}

// ---------------------------------------------------------------- o cursor que clica num botão
/* kCursorNoBotao(comp, alvo, rot, ox, oy, cresce, preferido, u): a ponta do clique (no espaço do pai do cursor) pela regra da guarda
   (gCursor): dentro do botão `alvo`, a setinha sem cobrir o rótulo `rot` (o retorno do texto(), com x, y, w, h no espaço do grupo do
   botão, que está em ox, oy e cresce até `cresce`× no clique). Escreve a linha GUARDA da cena com a caixa do cursor e a do rótulo. */
function kCursorNoBotao(comp, alvo, rot, ox, oy, cresce, preferido, u) {
    var k = cresce || 1, cx = ox || 0, cy = oy || 0, rx = rot && rot.w ? rot.x : 0, ry = rot && rot.w ? rot.y : 0;
    var rc0 = rot && rot.w ? [cx + (rx - rot.w / 2) * k, cy + (ry - rot.h / 2) * k, cx + (rx + rot.w / 2) * k, cy + (ry + rot.h / 2) * k] : null;
    var r = gCursor(alvo, rc0 ? [rc0] : [], { w: 48 * u, h: 70 * u, folga: 10 * u, preferido: preferido });
    var log = ['cursor: clica em [' + Math.round(r.ponta[0]) + ',' + Math.round(r.ponta[1]) + ']' + (r.movido ? ' (saiu de cima do texto)' : '') + (r.ok ? '' : ' na borda de baixo do botão')];
    writeLn('GUARDA ' + JSON.stringify({ cena: KIT.cenaIdx, w: comp.width, h: comp.height, log: log, caixas: [{ tipo: 'cursor', nome: 'cursor', c: [Math.round(r.c[0]), Math.round(r.c[1]), Math.round(r.c[2]), Math.round(r.c[3])], fora: 0 }].concat(rc0 ? [{ tipo: 'texto', nome: 'rótulo do botão', c: [Math.round(rc0[0]), Math.round(rc0[1]), Math.round(rc0[2]), Math.round(rc0[3])], fora: 0 }] : []), violacoes: gVerificar([{ tipo: 'cursor', nome: 'cursor', c: r.c }].concat(rc0 ? [{ tipo: 'texto', nome: 'rótulo', c: rc0, livre: false }] : []), comp.width, comp.height, { zona: { x0: -1e9, x1: 1e9, y0: -1e9, y1: 1e9, trilho: null } }) }));
    return r.ponta;
}

// ---------------------------------------------------------------- CTA
/* O CTA: título que voa, botão SÓLIDO com brilho que varre (Merge Paths em Intersect: o brilho só existe dentro da pílula) e um
   ícone; o cursor entra, o botão afunda e volta com quique, estoura em ondas e partículas, com clique + impacto. Sem camada fantasma. */
function cenaCta(comp, P, d) {
    var W = comp.width, H = comp.height, u = kU(comp), cam = kBase(comp, P), v = kVert(comp), T0 = 0.1, i;
    var tit = texto(comp, d.titulo || '', { tamanho: (v ? 140 : 116) * u, cor: P.fg, corDestaque: P.ac, x: W / 2, y: v ? H * 0.34 : H * 0.34, parent: cam.no, entrada: 'voa', t0: T0, largura: W * (v ? 0.86 : 0.74), maxLinhas: 3 });
    for (i = 0; i < tit.tempos.length; i++) som(tit.tempos[i], i % 2 ? 'tique' : 'pop', 0.5);
    var bw = v ? W * 0.84 : Math.min(W * 0.5, 780 * u), bh = (v ? 230 : 158) * u, bx = W / 2, by = v ? H * 0.66 : H * 0.7, tb = Math.max(tit.fim * 0.8, T0 + 0.5);
    var ink = kSobre(P);
    KIT.rects.push({ cena: KIT.cenaIdx, x0: bx - bw / 2, x1: bx + bw / 2, y0: by - bh / 2, y1: by + bh / 2, t0: 0, t1: 1, texto: false, sobrepoe: true, livre: true, nome: 'botao' });
    var btn = grupo(comp, { x: bx, y: by, parent: cam.no, nome: 'botao-cta' });
    // o corpo sólido + o brilho que varre, na MESMA camada de forma: pílula, paralelogramo, Merge Paths (Intersect), preenchimento branco
    var corpo = forma(comp, 'pilula', { x: 0, y: 0, w: bw, h: bh, cor: P.ac, traco: kMisturar(P.ac, '#FFFFFF', 0.55), tracoL: 5 * u, parent: btn, nome: 'botao-corpo' });
    try {
        var br = comp.layers.addShape(); br.name = 'botao-brilho'; kPai(br, btn);
        var gr = br.property('ADBE Root Vectors Group').addProperty('ADBE Vector Group'), vv = gr.property('ADBE Vectors Group');
        var pl = vv.addProperty('ADBE Vector Shape - Rect'); pl.property('ADBE Vector Rect Size').setValue([bw, bh]); pl.property('ADBE Vector Rect Roundness').setValue(bh / 2);
        var pg = vv.addProperty('ADBE Vector Shape - Group'), S0 = new Shape(), S1 = new Shape(), zz = [[0, 0], [0, 0], [0, 0], [0, 0]], lw = 120 * u, sk = 60 * u;
        S0.vertices = [[-bw / 2 - lw - sk, bh], [-bw / 2 - sk, bh], [-bw / 2, -bh], [-bw / 2 - lw, -bh]]; S0.inTangents = zz; S0.outTangents = zz; S0.closed = true;
        S1.vertices = [[bw / 2 + sk * 0, bh], [bw / 2 + lw + sk * 0, bh], [bw / 2 + lw + sk, -bh], [bw / 2 + sk, -bh]]; S1.inTangents = zz; S1.outTangents = zz; S1.closed = true;
        var sp = pg.property('ADBE Vector Shape'); sp.setValue(S0);
        var ts = tb + 0.55;
        sp.setValueAtTime(kFrame(ts), S0); sp.setValueAtTime(kFrame(ts) + 0.7, S1);
        sp.setValueAtTime(kFrame(ts) + 1.9, S0); sp.setValueAtTime(kFrame(ts) + 2.6, S1);   // varre de novo, sem parar
        var mg = vv.addProperty('ADBE Vector Filter - Merge'); mg.property('ADBE Vector Merge Type').setValue(4);
        vv.addProperty('ADBE Vector Graphic - Fill').property('ADBE Vector Fill Color').setValue([1, 1, 1]);
        kO(br).setValue(55);
        kP(br).setValue([0, 0]);
    } catch (e) { KIT.avisos.push('cena ' + KIT.cenaIdx + ': brilho do botão indisponível'); }
    // o ícone (seta) e o rótulo
    var ic = caminho(comp, [[-22 * u, 0], [24 * u, 0]], { cor: ink, esp: 11 * u, parent: btn, nome: 'seta-haste' });
    caminho(comp, [[6 * u, -20 * u], [26 * u, 0], [6 * u, 20 * u]], { cor: ink, esp: 11 * u, parent: btn, nome: 'seta-ponta' });
    var arrows = [];
    var rot = texto(comp, d.botao || 'Começar agora', { fonte: P.fonteTitulo, tamanho: (v ? 84 : 58) * u, cor: ink, corDestaque: ink, x: -28 * u, y: 0, parent: btn, entrada: 'sobe', t0: tb + 0.08, largura: bw * 0.7, maxLinhas: 1, sublinhado: false, livre: true, fala: false });
    var iconX = bw / 2 - 74 * u;
    var j, kids = [];
    // posiciona a seta à direita do rótulo
    for (j = 0; j < comp.numLayers; j++) { var Ly = comp.layer(j + 1); if (Ly.name == 'seta-haste' || Ly.name == 'seta-ponta') kP(Ly).setValue([iconX, 0]); }
    // entrada do botão: estoura de zero com quique forte
    mola(kS(btn), tb, [0, 0], [100, 100], { quique: 0.88, dur: 0.75 });
    som(tb, 'pop', 0.8);
    // o cursor entra, o botão afunda e explode
    var tp = tb + 1.05;
    var cur = kCursor(comp, cam.no, u, '#FFFFFF', '#101010');
    // o cursor é ELEMENTO para a guarda: a ponta clica dentro do botão, depois do texto (na seta) ou na borda; a setinha
    // inteira (48×70 a partir da ponta) nunca cobre o rótulo, nem com o botão crescendo 10% no estouro do clique
    var c1 = kCursorNoBotao(comp, [bx - bw / 2, by - bh / 2, bx + bw / 2, by + bh / 2], rot, bx, by, 1.1, [bx + iconX - 4 * u, by + 10 * u], u);
    var c0 = [c1[0] + bw * 0.1, by + 460 * u];
    kP(cur).setValue(c0);
    curva(kP(cur), [[tp - 0.75, c0, null, 18], [tp - 0.05, c1, 82, 40]]);
    curva(kO(cur), [[tp - 0.75, 0, null, 30], [tp - 0.55, 100, 60, null], [tp + 0.8, 100, null, 30], [tp + 1.05, 0, 60, null]]);
    curva(kS(cur), [[tp - 0.05, [100, 100], null, 25], [tp + 0.05, [78, 78], 70, 40], [tp + 0.25, [100, 100], 70, null]]);
    curva(kS(btn), [[tp - 0.02, [100, 100], null, 20], [tp + 0.07, [86, 86], 60, 30], [tp + 0.28, [110, 110], 40, 60], [tp + 0.6, [100, 100], 70, null]]);
    // ondas e partículas
    for (j = 0; j < 3; j++) {
        var on = forma(comp, 'pilula', { x: bx, y: by, w: bw, h: bh, traco: j == 1 ? P.fg : P.ac, tracoL: (9 - j * 2) * u, alfa: 0, parent: cam.no, nome: 'onda' });
        curva(kS(on), [[tp + j * 0.07, [100, 100], null, 20], [tp + j * 0.07 + 0.7, [150 + j * 14, 220 + j * 30], 80, null]]);
        curva(kO(on), [[tp + j * 0.07 - 0.02, 0, null, 30], [tp + j * 0.07, 85, null, 30], [tp + j * 0.07 + 0.7, 0, 70, null]]);
    }
    var cxBotao = [bx - bw / 2 * 1.1, by - bh / 2 * 1.1, bx + bw / 2 * 1.1, by + bh / 2 * 1.1];   // o botão a 110% no estouro
    kEstouro(comp, cam, bx, by, tp + 0.04, P.ac, 16, bw * 0.25, false, cxBotao);
    kEstouro(comp, cam, bx, by, tp + 0.08, P.fg, 10, bw * 0.18, false, cxBotao);
    som(tp, 'clique', 0.9); som(tp + 0.03, 'impacto', 0.6); som(tp + 0.1, 'brilho', 0.6);
    // depois do clique o botão respira e solta um halo, sem parar
    var halo = forma(comp, 'pilula', { x: bx, y: by, w: bw, h: bh, traco: P.ac, tracoL: 6 * u, alfa: 0, parent: cam.no, nome: 'halo' });
    kS(halo).expression = 'var q = ((time - ' + (tp + 0.7).toFixed(2) + ') * 0.9) % 1; time < ' + (tp + 0.7).toFixed(2) + ' ? [100, 100] : [100 + 28 * q, 100 + 70 * q]';
    kO(halo).expression = 'var q = ((time - ' + (tp + 0.7).toFixed(2) + ') * 0.9) % 1; time < ' + (tp + 0.7).toFixed(2) + ' ? 0 : 70 * (1 - q)';
    cam.empurra(0.9, 0, 10 * u, 1.02).empurra(tp, 0, 40 * u, 1.06).empurra(comp.duration - 0.2, 0, 60 * u, 1.08);
    fecha(comp);
}

// ---------------------------------------------------------------- montagem
/* O vocabulário de cenas: 8 clássicas (aqui) + 31 primitivas (primitivas.jsx; a 31ª, "foto", só entra quando o pedido traz foto). O diretor (maquina-diretor.js) escolhe. */
var CENAS = {
    gancho: cenaGancho, celular: cenaCelular, numero: cenaNumero, lista: cenaLista, antes_depois: cenaAntesDepois, icones: cenaIcones, citacao: cenaCitacao, cta: cenaCta,
    cinetico: cenaCinetico, interface: cenaInterface, mergulho: cenaMergulho, camera_nulls: cenaCameraNulls, barras: cenaBarras, linha: cenaLinha, mapa: cenaMapa,
    cards_pulam: cenaCardsPulam, cursor: cenaCursor, notificacoes: cenaNotificacoes, chat: cenaChat, linha_tempo: cenaLinhaTempo, laco: cenaLaco, explosao: cenaExplosao,
    revela: cenaRevela, split: cenaSplit, heroi: cenaHeroi, particulas: cenaParticulas, relogio: cenaRelogio, calendario: cenaCalendario, funil: cenaFunil,
    parede_logos: cenaParedeLogos, prompt: cenaPrompt, odometro: cenaOdometro, medidor: cenaMedidor, carimbo: cenaCarimbo, leque: cenaLeque, ondas: cenaOndas,
    rolagem: cenaRolagem, estrelas: cenaEstrelas, foto: cenaFoto
};

function montar(PL) {
    var W = PL.w, H = PL.h, fps = PL.fps || 30, n = PL.cenas.length, i;
    KIT.fps = fps; KIT.ajuste = PL.ajuste || 1;
    KIT.marca = { titulo: PL.fontes.titulo, destaque: PL.fontes.destaque, texto: PL.fontes.texto };
    KIT.tipo = PL.tipo || {};
    KIT.elementos = PL.elementos || [];
    KIT.voz = PL.voz || null;
    KIT.paletaBase = PL.paleta || null; KIT.debugFit = !!PL.debugFit; KIT.bpm = PL.bpm || 0; KIT.batidaFase = PL.batidaFase || 0; KIT.fx = PL.fx !== false;
    // a câmera dá zoom de até ~10%: o conteúdo é montado a 90% (87% com legenda, subido 7%) para NÃO sair da área segura quando a câmera chega perto
    KIT.enq = 0.88; KIT.enqDy = 0;
    if (PL.legenda && PL.legenda.chunks && PL.legenda.chunks.length) { KIT.enq = 0.85; KIT.enqDy = H * 0.07; }
    var starts = [], t = 0;
    for (i = 0; i < n; i++) { starts.push(PL.cenas[i].inicio != null ? PL.cenas[i].inicio : t); t = starts[i] + PL.cenas[i].dur; }
    var total = PL.duracao || t;
    var main = novaComp('MAIN', W, H, total, fps);
    KIT.main = main;
    // o fundo da MAIN é a cor de destaque: se as duas cenas se afastarem numa troca (chicote, empurra), o vão é uma faixa de cor, nunca preto
    main.layers.addSolid(kCor(PL.paleta && PL.paleta.destaque ? PL.paleta.destaque : '#FFFFFF'), 'fundo-main', W, H, 1);
    // um fundo por cena, embaixo de tudo, com a cor DA CENA: se a câmera ou a troca afastar a cena, a borda mostra a cor dela, não uma faixa de outra cor
    for (i = 0; i < n; i++) { var fb = main.layers.addSolid(kCor(PL.cenas[i].pal.bg), 'fundo-cena ' + i, W, H, 1); fb.inPoint = starts[i]; fb.outPoint = i < n - 1 ? Math.min(total, starts[i + 1] + TROCA_DUR + 0.05) : total; }
    var cam = [];
    for (i = 0; i < n; i++) {
        var c = PL.cenas[i], dur = c.dur + (i < n - 1 ? TROCA_DUR + 0.05 : 0), f = Math.max(1, Math.min(2.4, c.f || 1));
        // a cena é montada em tempo NOMINAL (dur × f) e tocada f vezes mais rápido dentro de uma comp-embrulho: tudo entra nos primeiros ~35%
        var comp = novaComp('CENA ' + i, W, H, dur * f, fps);
        // enfeite só onde há espaço livre de verdade (nada de herói em cima de janela, prompt ou botão)
        KIT.cenaComElemento = !!c.elemento; KIT.semAmbiente = !!c.semAmbiente || !{ cinetico: 1, gancho: 1, numero: 1, citacao: 1, particulas: 1, odometro: 1, relogio: 1, carimbo: 1 }[c.tipo];
        // (até 20/10 esta linha morava DEPOIS do comentário acima, na mesma linha: o cenaIdx, o t0, o ajuste e o f nunca eram postos)
        KIT.semRotulos = !!c.semRotulos; KIT.cenaHeroi = c.tipo == 'heroi' || c.tipo == 'revela'; KIT.titulosL = []; KIT.tituloB = null; KIT.t0 = starts[i]; KIT.cenaIdx = i; KIT.estilo = c.camera || PL.camera || 'deriva'; KIT.ajusteCena = c.ajuste || 1; KIT.tituloDesce = c.tituloDesce || 0; KIT.f = f; KIT.entMax = 0;
        var P = c.pal; KIT.pal = P;
        P.fonteTitulo = PL.fontes.titulo; P.fonteTexto = PL.fontes.texto; P.fonteDestaque = PL.fontes.destaque;
        if (!CENAS[c.tipo]) KIT.avisos.push('cena ' + i + ': tipo desconhecido ' + c.tipo);
        (CENAS[c.tipo] || cenaGancho)(comp, P, c);
        writeLn('ENTRADA ' + JSON.stringify({ cena: i, max: Math.round(kEntradaMax(comp) * 100) / 100, f: f, dur: c.dur }));
        var alvo = comp;
        if (f > 1.001) {
            alvo = novaComp('CENA ' + i + ' T', W, H, dur, fps);
            var R = alvo.layers.add(comp); R.stretch = 100 / f; R.startTime = 0; R.motionBlur = true;
        }
        KIT.f = 1;
        var L = main.layers.add(alvo);
        L.startTime = starts[i];
        L.inPoint = starts[i];
        L.outPoint = i < n - 1 ? Math.min(total, starts[i + 1] + TROCA_DUR + 0.05) : total;
        L.motionBlur = true;
        cam.push(L);
    }
    KIT.t0 = 0;   // as trocas usam o tempo do vídeo (o som() soma KIT.t0)
    for (i = 1; i < n; i++) troca(cam[i - 1], cam[i], starts[i], (PL.trocas && PL.trocas[i - 1]) || 'encolhe', PL.cenas[i].pal.ac);
    if (PL.legenda && PL.legenda.chunks && PL.legenda.chunks.length) legenda(main, PL.legenda.chunks, PL.legenda);
    for (i = 0; i < KIT.avisos.length; i++) writeLn('AVISO ' + KIT.avisos[i]);
    writeLn('ELEMENTOS ' + (KIT.nElem || 0));
    writeLn('TOTAL ' + total);
    return main;
}
