/* kit/tres.jsx — o PRODUTO EM 3D, feito de geometria, sem arquivo de fora.
 *
 *   "Crie um vídeo para anunciar um headset — o vídeo deve ter um 3D do fone"
 *   (30/09), com "sem referências": nada de foto, então o produto é modelado
 *   aqui, com as peças do three.js, e iluminado como em estúdio.
 *
 * {"tipo":"kit","peca":"produto3d","modelo":"fone"|"celular"|"lata"|"caixa"|"notebook"|"garrafa"|"relogio",
 *  "cor":"#1b1c20","corAcento":"#ff5a1f","t":0,"giro":0.35,
 *  "acabamento":"metal","camera":"alto","luz":"recorte","ritmo":"pendulo",   (variacao3d.js)
 *  "x":0,"y":0,"largura":1920,"altura":1080}
 *
 * O movimento: entra pequeno e girado e assenta com mola (0,9 s), depois gira
 * devagar e flutua — produto de vitrine, nunca pião. A luz de recorte vem na
 * cor de acento: é ela que desenha a silhueta no fundo escuro. */
import React, { useMemo } from 'react';
import { ThreeCanvas } from '@remotion/three';
import * as THREE from 'three';
import { useThree } from '@react-three/fiber';
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';

/* A LUZ DE ESTÚDIO: sem um ambiente, material brilhante ou metálico não tem o
   que refletir e sai preto ("a engrenagem parecia uma mancha", 30/09). A sala
   do three (RoomEnvironment, local, sem rede) vira o reflexo de tudo. */
function Ambiente({ forca = 1 }) {
  const { gl, scene } = useThree();
  useMemo(() => {
    const pm = new THREE.PMREMGenerator(gl);
    scene.environment = pm.fromScene(new RoomEnvironment(), 0.04).texture;
    scene.environmentIntensity = forca;
    pm.dispose();
  }, [gl, scene]);
  return null;
}

const saida = (x) => 1 - Math.pow(1 - Math.max(0, Math.min(1, x)), 3);

function Material({ cor, brilho = 0.35, metal = 0.25, verniz = 0.8, iris = 0, emissivo }) {
  return <meshPhysicalMaterial color={cor} roughness={brilho} metalness={metal} clearcoat={verniz} clearcoatRoughness={0.25} iridescence={iris}
    {...(emissivo ? { emissive: emissivo, emissiveIntensity: 1.2 } : {})} />;
}

// O fone: o arco, os deslizadores, as conchas com o anel de acento, as almofadas e o logo aceso.
/* AS VARIAÇÕES (variacao3d.js sorteia os nomes): o acabamento do corpo, o
   enquadramento, a luz e o ritmo. Sem nome, o de sempre. */
const ACABAMENTOS = {
  verniz: { brilho: 0.3, metal: 0.3, verniz: 0.8 }, metal: { brilho: 0.18, metal: 0.92, verniz: 0.3 },
  fosco: { brilho: 0.78, metal: 0, verniz: 0 }, perola: { brilho: 0.25, metal: 0.1, verniz: 1, iris: 1 },
  acetinado: { brilho: 0.5, metal: 0.45, verniz: 0.4 },
};
const CAMERAS = {
  frente: {}, alto: { y: 1.5, inclina: 0.12 }, baixo: { y: -0.6, inclina: -0.1 },
  perfil: { giro0: -1.7, roll: 0.07 }, perto: { fov: 25 },
};
// [chave, preenchimento, recorte na cor de acento, ambiente]
const LUZES = { estudio: [3.2, 1.1, 3.2, 0.35], recorte: [1.6, 0.4, 5.5, 0.15], suave: [2.2, 2.0, 1.4, 0.7], lateral: [4.2, 0.2, 2.6, 0.2] };
const RITMOS = { lento: 0.18, medio: 0.35, rapido: 0.6 };

function Fone({ cor, acento, fin }) {
  const R = 1.05;
  const concha = (lado) => (
    <group position={[lado * R, -0.12, 0]}>
      <mesh rotation={[0, 0, Math.PI / 2]}>
        <cylinderGeometry args={[0.44, 0.46, 0.26, 64]} />
        <Material cor={cor} brilho={0.3} metal={0.35} {...fin} />
      </mesh>
      <mesh position={[lado * 0.135, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
        <torusGeometry args={[0.43, 0.035, 24, 96]} />
        <Material cor={acento} brilho={0.18} metal={0.9} verniz={1} />
      </mesh>
      <mesh position={[lado * 0.14, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
        <circleGeometry args={[0.16, 48]} />
        <Material cor={acento} brilho={0.2} metal={0.2} emissivo={acento} />
      </mesh>
      <mesh position={[-lado * 0.16, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
        <torusGeometry args={[0.3, 0.11, 32, 96]} />
        <Material cor="#141416" brilho={0.85} metal={0} verniz={0} />
      </mesh>
    </group>
  );
  return (
    <group>
      <mesh>
        <torusGeometry args={[R, 0.075, 32, 160, Math.PI]} />
        <Material cor={cor} brilho={0.28} metal={0.4} {...fin} />
      </mesh>
      <mesh position={[0, -0.06, 0]} scale={[1, 1, 1.6]}>
        <torusGeometry args={[R - 0.07, 0.05, 24, 140, Math.PI]} />
        <Material cor="#141416" brilho={0.9} metal={0} verniz={0} />
      </mesh>
      {[-1, 1].map((lado) => (
        <mesh key={lado} position={[lado * R, 0.1, 0]}>
          <boxGeometry args={[0.07, 0.34, 0.12]} />
          <Material cor={acento} brilho={0.15} metal={0.95} verniz={1} />
        </mesh>
      ))}
      {concha(-1)}
      {concha(1)}
    </group>
  );
}

function Celular({ cor, acento, fin }) {
  return (
    <group rotation={[0, 0, 0.05]}>
      <mesh><boxGeometry args={[1.0, 2.05, 0.1]} /><Material cor={cor} brilho={0.22} metal={0.7} {...fin} /></mesh>
      <mesh position={[0, 0, 0.052]}><planeGeometry args={[0.92, 1.96]} /><Material cor="#0b0c10" brilho={0.1} metal={0} emissivo="#0d1830" /></mesh>
      <mesh position={[0, 0.3, 0.054]}><circleGeometry args={[0.22, 48]} /><Material cor={acento} emissivo={acento} /></mesh>
      <mesh position={[0.26, 0.78, -0.07]} rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[0.14, 0.14, 0.05, 48]} /><Material cor="#222" brilho={0.1} metal={0.9} /></mesh>
    </group>
  );
}

function Lata({ cor, acento, fin }) {
  return (
    <group>
      <mesh><cylinderGeometry args={[0.55, 0.55, 1.9, 96]} /><Material cor={cor} brilho={0.2} metal={0.85} {...fin} /></mesh>
      <mesh position={[0, 0.2, 0]}><cylinderGeometry args={[0.556, 0.556, 0.5, 96, 1, true]} /><Material cor={acento} brilho={0.3} metal={0.3} /></mesh>
      <mesh position={[0, 0.97, 0]}><cylinderGeometry args={[0.5, 0.55, 0.06, 96]} /><Material cor="#c9ccd2" brilho={0.25} metal={1} /></mesh>
    </group>
  );
}

function Caixa({ cor, acento, fin }) {
  return (
    <group>
      <mesh><boxGeometry args={[1.5, 1.5, 1.5]} /><Material cor={cor} brilho={0.3} metal={0.3} {...fin} /></mesh>
      <mesh position={[0, 0, 0.752]}><planeGeometry args={[1.0, 0.18]} /><Material cor={acento} emissivo={acento} /></mesh>
    </group>
  );
}

// O notebook: a base, o teclado escuro, a tampa aberta a ~105° e a tela acesa.
function Notebook({ cor, acento, fin }) {
  return (
    <group position={[0, -0.3, 0.1]} rotation={[0.1, 0, 0]} scale={[0.7, 0.7, 0.7]}>{/* a 1,0 a tela saía cortada no topo (05/10) */}
      <mesh><boxGeometry args={[2.2, 0.08, 1.45]} /><Material cor={cor} brilho={0.25} metal={0.75} {...fin} /></mesh>
      <mesh position={[0, 0.042, -0.12]}><boxGeometry args={[1.9, 0.01, 0.75]} /><Material cor="#141416" brilho={0.8} metal={0} verniz={0} /></mesh>
      <mesh position={[0, 0.042, 0.45]}><boxGeometry args={[0.7, 0.01, 0.38]} /><Material cor="#26272c" brilho={0.4} metal={0.2} /></mesh>
      <group position={[0, 0.04, -0.72]} rotation={[-0.26, 0, 0]}>
        <mesh position={[0, 0.72, 0]}><boxGeometry args={[2.2, 1.44, 0.06]} /><Material cor={cor} brilho={0.25} metal={0.75} {...fin} /></mesh>
        <mesh position={[0, 0.74, 0.032]}><planeGeometry args={[2.0, 1.25]} /><Material cor="#0b0c10" brilho={0.1} metal={0} emissivo="#0d1830" /></mesh>
        <mesh position={[0, 0.74, 0.034]}><circleGeometry args={[0.2, 48]} /><Material cor={acento} emissivo={acento} /></mesh>
      </group>
    </group>
  );
}

// A garrafa: o corpo, o ombro, o gargalo, a tampa e o rótulo na cor de acento.
function Garrafa({ cor, acento, fin }) {
  return (
    <group position={[0, -0.15, 0]}>
      <mesh><cylinderGeometry args={[0.5, 0.5, 1.5, 96]} /><Material cor={cor} brilho={0.15} metal={0.6} {...fin} /></mesh>
      <mesh position={[0, 0.95, 0]}><cylinderGeometry args={[0.2, 0.5, 0.4, 96]} /><Material cor={cor} brilho={0.15} metal={0.6} {...fin} /></mesh>
      <mesh position={[0, 1.3, 0]}><cylinderGeometry args={[0.2, 0.2, 0.3, 64]} /><Material cor={cor} brilho={0.15} metal={0.6} {...fin} /></mesh>
      <mesh position={[0, 1.52, 0]}><cylinderGeometry args={[0.23, 0.23, 0.18, 64]} /><Material cor={acento} brilho={0.3} metal={0.8} /></mesh>
      <mesh position={[0, 0.05, 0]}><cylinderGeometry args={[0.506, 0.506, 0.55, 96, 1, true]} /><Material cor={acento} brilho={0.5} metal={0.1} verniz={0.3} /></mesh>
    </group>
  );
}

// O relógio: a caixa redonda, o aro de acento, o mostrador aceso e a pulseira.
function Relogio({ cor, acento, fin }) {
  return (
    <group>
      <mesh rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[0.62, 0.62, 0.22, 96]} /><Material cor={cor} brilho={0.2} metal={0.85} {...fin} /></mesh>
      <mesh position={[0, 0, 0.11]}><torusGeometry args={[0.6, 0.045, 24, 96]} /><Material cor={acento} brilho={0.15} metal={0.95} verniz={1} /></mesh>
      <mesh position={[0, 0, 0.112]}><circleGeometry args={[0.55, 64]} /><Material cor="#0b0c10" brilho={0.1} metal={0} emissivo="#0d1830" /></mesh>
      <mesh position={[0, 0.16, 0.116]}><boxGeometry args={[0.04, 0.32, 0.01]} /><Material cor={acento} emissivo={acento} /></mesh>
      <mesh position={[0.1, 0, 0.118]} rotation={[0, 0, -Math.PI / 2]}><boxGeometry args={[0.05, 0.22, 0.01]} /><Material cor="#f4f4f6" emissivo="#9aa0aa" /></mesh>
      <mesh position={[0.66, 0, 0]} rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[0.06, 0.06, 0.14, 24]} /><Material cor={cor} brilho={0.2} metal={0.85} {...fin} /></mesh>
      {[-1, 1].map((l) => <mesh key={l} position={[0, l * 1.0, -0.02]}><boxGeometry args={[0.6, 0.85, 0.08]} /><Material cor="#1a1a1d" brilho={0.7} metal={0} verniz={0.2} /></mesh>)}
    </group>
  );
}

const MODELOS = { fone: Fone, headset: Fone, celular: Celular, phone: Celular, lata: Lata, garrafa: Garrafa, caixa: Caixa,
  notebook: Notebook, laptop: Notebook, relogio: Relogio, watch: Relogio };
export const MODELOS_3D = Object.keys(MODELOS);

export function Produto3D({ el, t }) {
  const W = el.largura || 1920, H = el.altura || 1080;
  const t0 = el.t || 0;
  const tl = Math.max(0, t - t0);
  const k = saida(tl / 0.9);
  const Modelo = MODELOS[el.modelo] || Caixa;
  const cor = el.cor || '#1b1c20';
  const acento = el.corAcento || '#ff5a1f';
  const cam = CAMERAS[el.camera] || {};
  const luz = LUZES[el.luz] || LUZES.estudio;
  const fin = ACABAMENTOS[el.acabamento];
  // O ritmo: o giro contínuo em três velocidades, ou o pêndulo, que vai e volta mostrando a frente.
  const anda = el.ritmo === 'pendulo' ? Math.sin(tl * 0.9) * 0.7 : tl * (RITMOS[el.ritmo] ?? el.giro ?? 0.35);
  const giro = (el.giro0 ?? cam.giro0 ?? -0.9) + (1 - k) * 1.4 + anda;
  const escala = (el.escala || 1) * (0.72 + 0.28 * k);
  const flutua = Math.sin(tl * 1.3) * 0.05;
  return (
    <div style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: W, height: H, opacity: t >= t0 ? Math.min(1, tl / 0.25) : 0 }}>
      <ThreeCanvas width={W} height={H} camera={{ position: [0, cam.y ?? 0.35, 4.4], fov: el.fov || cam.fov || 32 }} gl={{ antialias: true, preserveDrawingBuffer: true }}>
        <Ambiente forca={0.8} />
        <hemisphereLight args={['#ffffff', '#202020', 0.5]} />
        <ambientLight intensity={luz[3]} />
        <directionalLight position={el.luz === 'lateral' ? [5, 1.5, 1.5] : [3, 4, 5]} intensity={luz[0]} />
        <directionalLight position={[-2, 3, 4]} intensity={luz[1]} />
        <directionalLight position={[-4, 1.5, -3]} intensity={luz[2]} color={acento} />
        <pointLight position={[0, -2, 3]} intensity={0.8} />
        <group position={[0, flutua - 0.05, 0]} rotation={[0.18 + (cam.inclina || 0) + (1 - k) * 0.3, giro, cam.roll || 0]} scale={[escala, escala, escala]}>
          <Modelo cor={cor} acento={acento} fin={fin} />
        </group>
        {/* O chão. O gerativo dispensa (semChao): o disco virava uma faixa escura atrás do texto. */}
        {!el.semChao && (
          <mesh position={[0, -1.25, 0]} rotation={[-Math.PI / 2, 0, 0]}>
            <circleGeometry args={[2.4, 64]} />
            <meshStandardMaterial color="#0a0a0c" roughness={0.35} metalness={0.6} transparent opacity={0.85} />
          </mesh>
        )}
      </ThreeCanvas>
    </div>
  );
}

/* ── OS ÍCONES 3D ─────────────────────────────────────────────────────────
 * "A gente não tem ícone 3D" (30/09). Nada de emoji-3D de banco: cada ícone
 * é geometria daqui — forma 2D extrudada com chanfro (raio, coração,
 * estrela, escudo, balão, play, check, seta, engrenagem) ou primitivas
 * compostas (bateria, cadeado, relógio, barras, fone, celular, sol, onda) —
 * no material e na paleta do vídeo (argila, verniz ou metal), com a luz de
 * recorte na cor de acento.
 *
 * {"tipo":"kit","peca":"icone3d","icone":"raio","x":0,"y":0,"tamanho":360,
 *  "material":"argila","cor":"#efe8da","cor2":"#7a1f1f","luz":"#7a1f1f","t":0} */
function forma(pts, curvas) {
  const sh = new THREE.Shape();
  pts.forEach(([x, y], i) => (i ? sh.lineTo(x, y) : sh.moveTo(x, y)));
  if (curvas) curvas(sh);
  sh.closePath();
  return sh;
}
const FORMAS = {
  raio: () => forma([[0.15, 1], [-0.55, -0.1], [-0.05, -0.1], [-0.25, -1], [0.55, 0.15], [0.05, 0.15], [0.35, 1]]),
  estrela: () => forma(Array.from({ length: 10 }, (_, i) => { const a = Math.PI / 2 + (i * Math.PI) / 5, r = i % 2 ? 0.42 : 1; return [Math.cos(a) * r, Math.sin(a) * r]; })),
  play: () => forma([[-0.55, 0.85], [0.85, 0], [-0.55, -0.85]]),
  check: () => forma([[-0.95, 0.05], [-0.6, 0.4], [-0.25, 0.05], [0.6, 0.9], [0.95, 0.55], [-0.25, -0.65]]),
  seta: () => forma([[-0.9, 0.22], [0.2, 0.22], [0.2, 0.62], [0.95, 0], [0.2, -0.62], [0.2, -0.22], [-0.9, -0.22]]),
  coracao: () => {
    const sh = new THREE.Shape();
    sh.moveTo(0, -0.9);
    sh.bezierCurveTo(-0.2, -0.65, -1, -0.2, -1, 0.3);
    sh.bezierCurveTo(-1, 0.75, -0.6, 0.95, -0.35, 0.95);
    sh.bezierCurveTo(-0.15, 0.95, 0, 0.8, 0, 0.62);
    sh.bezierCurveTo(0, 0.8, 0.15, 0.95, 0.35, 0.95);
    sh.bezierCurveTo(0.6, 0.95, 1, 0.75, 1, 0.3);
    sh.bezierCurveTo(1, -0.2, 0.2, -0.65, 0, -0.9);
    return sh;
  },
  escudo: () => {
    const sh = new THREE.Shape();
    sh.moveTo(0, 1); sh.lineTo(0.85, 0.7); sh.lineTo(0.8, 0);
    sh.bezierCurveTo(0.75, -0.55, 0.35, -0.85, 0, -1);
    sh.bezierCurveTo(-0.35, -0.85, -0.75, -0.55, -0.8, 0);
    sh.lineTo(-0.85, 0.7); sh.closePath();
    return sh;
  },
  balao: () => {
    const sh = new THREE.Shape();
    const r = 0.3, x0 = -0.95, x1 = 0.95, y0 = -0.45, y1 = 0.8;
    sh.moveTo(x0 + r, y1); sh.lineTo(x1 - r, y1); sh.quadraticCurveTo(x1, y1, x1, y1 - r); sh.lineTo(x1, y0 + r);
    sh.quadraticCurveTo(x1, y0, x1 - r, y0); sh.lineTo(-0.15, y0); sh.lineTo(-0.55, -0.95); sh.lineTo(-0.5, y0);
    sh.lineTo(x0 + r, y0); sh.quadraticCurveTo(x0, y0, x0, y0 + r); sh.lineTo(x0, y1 - r); sh.quadraticCurveTo(x0, y1, x0 + r, y1);
    return sh;
  },
  engrenagem: () => {
    const n = 8, pts = [];
    for (let i = 0; i < n * 4; i++) { const a = (i / (n * 4)) * Math.PI * 2, r = i % 4 < 2 ? 1 : 0.74; pts.push([Math.cos(a) * r, Math.sin(a) * r]); }
    const sh = forma(pts);
    const furo = new THREE.Path(); furo.absarc(0, 0, 0.34, 0, Math.PI * 2, true); sh.holes.push(furo);
    return sh;
  },
};

function Mat({ tipo, cor }) {
  // Metal escovado, bala (verniz), pérola, acetinado ou argila — todos refletem a sala (Ambiente).
  if (tipo === 'metal') return <meshPhysicalMaterial color={cor} roughness={0.32} metalness={0.85} clearcoat={0.4} envMapIntensity={1.2} />;
  // Pérola (iridescente) e acetinado (meio metal, meio fosco): o cardápio de 09/10 (gerativo.MATERIAIS_ICONE).
  if (tipo === 'perola') return <meshPhysicalMaterial color={cor} roughness={0.25} metalness={0.1} clearcoat={1} clearcoatRoughness={0.1} iridescence={1} iridescenceIOR={1.3} />;
  if (tipo === 'acetinado') return <meshPhysicalMaterial color={cor} roughness={0.5} metalness={0.45} clearcoat={0.4} />;
  if (tipo === 'verniz') return <meshPhysicalMaterial color={cor} roughness={0.18} metalness={0} clearcoat={1} clearcoatRoughness={0.08} sheen={0.3} />;
  return <meshPhysicalMaterial color={cor} roughness={0.72} metalness={0} clearcoat={0.15} />;   // argila: fosca, macia
}

function Extrudado({ nome, m, cor }) {
  const geo = useMemo(() => new THREE.ExtrudeGeometry(FORMAS[nome](), { depth: 0.42, bevelEnabled: true, bevelThickness: 0.12, bevelSize: 0.08, bevelSegments: 4, curveSegments: 18 }), [nome]);
  return <mesh geometry={geo} position={[0, 0, -0.27]}><Mat tipo={m} cor={cor} /></mesh>;
}

function Composto({ nome, m, cor, cor2, tl }) {
  const caixa = (pos, tam, c = cor, rot) => <mesh position={pos} rotation={rot}><boxGeometry args={tam} /><Mat tipo={m} cor={c} /></mesh>;
  switch (nome) {
    case 'bateria': return (
      <group>
        {caixa([0, 0, 0], [1.7, 0.95, 0.5])}
        {caixa([0.95, 0, 0], [0.16, 0.4, 0.3])}
        {[0, 1, 2].map((i) => caixa([-0.52 + i * 0.52, 0, 0.27], [0.36, 0.62, 0.08], i <= Math.floor(tl * 4) % 3 + 0.5 ? cor2 : cor))}
      </group>
    );
    case 'cadeado': return (
      <group>
        {caixa([0, -0.3, 0], [1.3, 1.0, 0.5])}
        <mesh position={[0, 0.25, 0]}><torusGeometry args={[0.42, 0.11, 20, 48, Math.PI]} /><Mat tipo={m} cor={cor2} /></mesh>
        <mesh position={[0, -0.3, 0.27]} rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[0.1, 0.1, 0.06, 24]} /><Mat tipo={m} cor={cor2} /></mesh>
      </group>
    );
    case 'relogio': return (
      <group>
        <mesh rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[1, 1, 0.32, 64]} /><Mat tipo={m} cor={cor} /></mesh>
        <mesh position={[0, 0, 0.17]}><torusGeometry args={[0.98, 0.07, 16, 64]} /><Mat tipo={m} cor={cor2} /></mesh>
        <group position={[0, 0, 0.2]} rotation={[0, 0, -tl * 2.2]}>{caixa([0, 0.33, 0], [0.09, 0.66, 0.05], cor2)}</group>
        <group position={[0, 0, 0.22]} rotation={[0, 0, -tl * 0.4 - 1]}>{caixa([0, 0.22, 0], [0.12, 0.44, 0.05], cor2)}</group>
      </group>
    );
    case 'barras': return (
      <group>{[0.55, 0.95, 1.5].map((h, i) => caixa([-0.6 + i * 0.6, -0.75 + (h * Math.min(1, tl * 2.4 - i * 0.25 > 0 ? Math.min(1, tl * 2.4 - i * 0.25) : 0.05)) / 2, 0],
        [0.42, Math.max(0.05, h * Math.min(1, Math.max(0.05, tl * 2.4 - i * 0.25))), 0.42], i === 2 ? cor2 : cor))}</group>
    );
    case 'fone': return (
      <group>
        <mesh><torusGeometry args={[0.85, 0.11, 20, 64, Math.PI]} /><Mat tipo={m} cor={cor} /></mesh>
        {[-1, 1].map((l) => <mesh key={l} position={[l * 0.85, -0.25, 0]} rotation={[0, 0, Math.PI / 2]}><cylinderGeometry args={[0.36, 0.36, 0.3, 40]} /><Mat tipo={m} cor={cor2} /></mesh>)}
      </group>
    );
    case 'celular': return (
      <group>
        {caixa([0, 0, 0], [1.05, 1.9, 0.16])}
        {caixa([0, 0.02, 0.085], [0.9, 1.65, 0.02], cor2)}
      </group>
    );
    case 'sol': return (
      <group rotation={[0, 0, tl * 0.5]}>
        <mesh><sphereGeometry args={[0.52, 48, 32]} /><Mat tipo={m} cor={cor2} /></mesh>
        {Array.from({ length: 8 }, (_, i) => <mesh key={i} position={[Math.cos(i * Math.PI / 4) * 0.85, Math.sin(i * Math.PI / 4) * 0.85, 0]} rotation={[0, 0, i * Math.PI / 4 - Math.PI / 2]}>
          <coneGeometry args={[0.13, 0.36, 20]} /><Mat tipo={m} cor={cor} /></mesh>)}
      </group>
    );
    case 'onda': return (
      <group>{[0.5, 1.1, 1.7, 1.1, 0.5].map((h, i) => { const k = 0.55 + 0.45 * Math.abs(Math.sin(tl * 5 + i * 1.1)); return caixa([-0.9 + i * 0.45, 0, 0], [0.26, h * k, 0.26], i === 2 ? cor2 : cor); })}</group>
    );
    default: return null;
  }
}

export const ICONES = [...Object.keys(FORMAS), 'bateria', 'cadeado', 'relogio', 'barras', 'fone', 'celular', 'sol', 'onda'];

export function Icone3D({ el, t }) {
  const tam = Math.round(el.tamanho || 360);
  const tl = Math.max(0, t - (el.t || 0));
  // Entra com mola que passa um pouco (o ícone tem peso) e gira até de frente.
  const k = 1 - Math.exp(-7 * tl) * Math.cos(9 * tl);
  const giro = (1 - saida(tl / 0.8)) * -1.6 + Math.sin(tl * 1.1) * 0.35;
  const m = el.material || 'argila';
  return (
    <div style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: tam, height: tam, opacity: t >= (el.t || 0) ? Math.min(1, tl / 0.12) : 0 }}>
      {/* A sombra de contato: o objeto pousa, não flutua solto. */}
      <div style={{ position: 'absolute', left: '18%', width: '64%', top: '84%', height: '10%', borderRadius: '50%', background: 'radial-gradient(ellipse, rgba(0,0,0,0.42) 0%, transparent 70%)', transform: 'scale(' + (0.8 + 0.2 * k).toFixed(3) + ')' }} />
      <ThreeCanvas width={tam} height={tam} camera={{ position: [0, 0, 5.2], fov: 30 }} gl={{ antialias: true, preserveDrawingBuffer: true, alpha: true }}>
        <Ambiente forca={1.1} />
        <hemisphereLight args={['#ffffff', '#3a3a3a', 0.6]} />
        <ambientLight intensity={0.25} />
        <directionalLight position={[2.5, 3, 4]} intensity={2.6} />
        <directionalLight position={[-3, -1, -2]} intensity={2.4} color={el.luz || '#ffffff'} />
        <pointLight position={[0, 0, 3]} intensity={0.5} />
        <group rotation={[0.25 + Math.sin(tl * 0.8) * 0.08, giro, Math.sin(tl * 0.6) * 0.06]} scale={[k, k, k]} position={[0, Math.sin(tl * 1.4) * 0.06, 0]}>
          {FORMAS[el.icone] ? <Extrudado nome={el.icone} m={m} cor={el.cor || '#eeeeee'} /> : <Composto nome={el.icone} m={m} cor={el.cor || '#eeeeee'} cor2={el.cor2 || el.cor || '#ff5a1f'} tl={tl} />}
        </group>
      </ThreeCanvas>
    </div>
  );
}


/* ── OS HERÓIS EM 3D DE VERDADE (10/10) ───────────────────────────────────
 * "As animações são muito iguais, quero um 3d bem presente" (Joel, com um
 * reel de motion de referência). O herói abstrato era SVG — pontos girando
 * num plano. Estes três são three.js: perspectiva, profundidade, luz e
 * câmera que anda. Grandes: o raio vem do vazio do bloco (gerativo.jsx).
 *
 *   globo3d    a esfera de pontos e malha, girando inclinada, com um anel
 *              de órbita; os pontos de perto maiores (perspectiva real);
 *   fita3d     o nó que se torce, em material com verniz e a luz de
 *              recorte na cor de acento;
 *   colunas3d  o campo de colunas que sobe em onda, sobrevoado pela câmera
 *              e sumindo na névoa da cor do fundo — fica ATRÁS do texto.
 *
 * Canvas do quadro inteiro, transparente: o objeto é posto no lugar do
 * vazio convertendo pixel → mundo (câmera fixa, fov 30, a 12 unidades).
 */
const FOV3D = 30, DIST3D = 12;
const VIS3D = 2 * DIST3D * Math.tan((FOV3D / 2) * Math.PI / 180);
const mola3d = (x) => { const k = Math.max(0, Math.min(1, x)); return 1 - Math.pow(1 - k, 3) * Math.cos(k * 4.2); };

function CameraFixa({ pos, alvo, teto }) {
  const { camera } = useThree();
  // De ponta-cabeça: o campo pende do alto (o texto mora embaixo).
  camera.up.set(0, teto ? -1 : 1, 0);
  camera.position.set(pos[0], pos[1], pos[2]);
  camera.near = 0.1; camera.far = 80;
  camera.lookAt(alvo[0], alvo[1], alvo[2]);
  camera.updateProjectionMatrix();
  return null;
}

function Globo({ r, cor, cor2, sem, tl }) {
  const geo = useMemo(() => {
    const N = 1100, pos = new Float32Array(N * 3), col = new Float32Array(N * 3);
    const c1 = new THREE.Color(cor2), c2 = new THREE.Color(cor);
    const ouro = Math.PI * (3 - Math.sqrt(5));
    for (let i = 0; i < N; i++) {
      const y = 1 - (i / (N - 1)) * 2, rr = Math.sqrt(1 - y * y), a = i * ouro;
      pos.set([Math.cos(a) * rr * r, y * r, Math.sin(a) * rr * r], i * 3);
      const c = (i * 7 + sem) % 6 === 0 ? c2 : c1;
      col.set([c.r, c.g, c.b], i * 3);
    }
    const g = new THREE.BufferGeometry();
    g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
    g.setAttribute('color', new THREE.BufferAttribute(col, 3));
    return g;
  }, [r, cor, cor2, sem]);
  return (
    <group rotation={[0.38 + Math.sin(tl * 0.5) * 0.08, tl * 0.55 + sem * 0.01, 0.12]}>
      <points geometry={geo}><pointsMaterial size={r * 0.06} vertexColors sizeAttenuation /></points>
      <mesh><sphereGeometry args={[r * 0.99, 32, 20]} /><meshBasicMaterial color={cor2} wireframe transparent opacity={0.22} /></mesh>
      <mesh rotation={[1.2, 0.3, tl * 0.4]}><torusGeometry args={[r * 1.14, r * 0.008, 8, 160]} /><meshBasicMaterial color={cor} transparent opacity={0.85} /></mesh>
      <mesh position={[Math.cos(tl * 1.3) * r * 1.14, 0, Math.sin(tl * 1.3) * r * 1.14]} rotation={[1.2, 0.3, 0]}>
        <sphereGeometry args={[r * 0.05, 16, 12]} /><meshBasicMaterial color={cor} />
      </mesh>
    </group>
  );
}

function Fita({ r, cor, cor2, sem, tl }) {
  return (
    <group rotation={[0.5 + tl * 0.35, tl * 0.6 + sem * 0.02, 0.2]}>
      <mesh>
        <torusKnotGeometry args={[r * 0.52, r * 0.13, 280, 32, 2 + (sem % 2), 3]} />
        <meshPhysicalMaterial color={cor2} roughness={0.28} metalness={0.35} clearcoat={0.9} clearcoatRoughness={0.2} />
      </mesh>
      <mesh scale={[1.02, 1.02, 1.02]}>
        <torusKnotGeometry args={[r * 0.52, r * 0.13, 140, 12, 2 + (sem % 2), 3]} />
        <meshBasicMaterial color={cor} wireframe transparent opacity={0.22} />
      </mesh>
    </group>
  );
}

function Colunas({ cor, cor2, fundo, sem, tl }) {
  const n = 15, passo = 0.9;
  const base = new THREE.Color(fundo).lerp(new THREE.Color(cor2), 0.22).getStyle();
  const acesa = new THREE.Color(cor).getStyle();
  const els = [];
  for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) {
    const x = (i - (n - 1) / 2) * passo, z = (j - (n - 1) / 2) * passo - tl * 1.1;
    const d = Math.hypot(i - n / 2, j - n / 2);
    const onda = 0.5 + 0.5 * Math.sin(d * 0.85 - tl * 2.6 + (sem % 7));
    const h = 0.25 + 2.2 * onda * onda;
    const crista = onda > 0.93;
    els.push(
      <mesh key={i + '-' + j} position={[x, h / 2 - 1.6, z]} scale={[passo * 0.78, h, passo * 0.78]}>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color={crista ? acesa : base} roughness={0.55} metalness={0.15}
          {...(crista ? { emissive: acesa, emissiveIntensity: 0.6 } : {})} />
      </mesh>,
    );
  }
  return <group>{els}</group>;
}

/* O quanto cada forma passa do raio r, no pior quadro: o anel e a lua do
   globo (1,14 r + a lua), o nó (o tubo vai a 1,5 × 0,52 r + 0,13 r). */
const ALCANCE3D = { globo3d: 1.2, fita3d: 0.92 };

export function Heroi3D({ h, t, de, ate, W, H, slot }) {
  const tl = Math.max(0, t - de), dur = Math.max(0.8, ate - de);
  const k = Math.min(1.02, mola3d(tl / 0.8));
  // Some ANTES de o texto sair (a saída dele voa pela tela): o herói nunca cruza a letra.
  const saiDe = slot.saiDe != null ? slot.saiDe : ate - 0.25;
  const sai = Math.max(0, Math.min(1, (t - saiDe) / 0.2));
  const sem = h.semente || 0;
  const cor = h.cor || '#ff5a1f', cor2 = h.cor2 || '#eeeeee', fundo = h.fundo || '#111111';
  if (h.tipo === 'colunas3d') {
    // O sobrevoo: a câmera alta, olhando para a frente e para baixo, andando sobre o campo.
    const avanca = tl * 0.25;
    return (
      <div style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, opacity: (slot.op != null ? slot.op : 0.85) * Math.min(1, tl / 0.5) * (1 - sai),
        /* A FAIXA: as colunas só existem fora do texto (clip), com a borda esfumada. */
        ...(slot.faixa ? { clipPath: `inset(${slot.faixa.y0}px 0 ${H - slot.faixa.y1}px 0)`,
          WebkitMaskImage: slot.teto ? `linear-gradient(to top, transparent ${H - slot.faixa.y1}px, #000 ${H - slot.faixa.y1 + 0.06 * H}px)` : `linear-gradient(to bottom, transparent ${slot.faixa.y0}px, #000 ${slot.faixa.y0 + 0.06 * H}px)` } : {}) }}>
        <ThreeCanvas width={W} height={H} camera={{ position: [0, 4, 9], fov: 38 }} gl={{ antialias: true, preserveDrawingBuffer: true, alpha: true }}>
          {/* Câmera baixa olhando quase reto: o campo ocupa a metade de baixo e some na névoa — o meio fica para o texto. */}
          <CameraFixa teto={slot.teto} pos={[Math.sin(tl * 0.3) * 1.2, 2.3, 8 - avanca]} alvo={[0, 0.9, -4 - avanca]} />
          <fog attach="fog" args={[fundo, 6, 19]} />
          <ambientLight intensity={0.45} />
          <directionalLight position={[4, 8, 5]} intensity={2.2} />
          <directionalLight position={[-6, 2, -4]} intensity={2.6} color={cor} />
          <Colunas cor={cor} cor2={cor2} fundo={fundo} sem={sem} tl={tl} />
        </ThreeCanvas>
      </div>
    );
  }
  const { cx, cy, R } = slot;
  const wx = ((cx - W / 2) / H) * VIS3D, wy = (-(cy - H / 2) / H) * VIS3D;
  /* R é o TETO do que aparece na tela (o vão livre ao lado do texto). O raio
     no mundo sai dele de trás para a frente: o alcance da forma, a escala
     máxima e a perspectiva do ponto mais perto da lente no fim do dolly. */
  const F = ALCANCE3D[h.tipo] || 1.2, zMax = 0.45;
  let r = (R / H) * VIS3D / (F * 1.02);
  for (let i = 0; i < 3; i++) r = ((R / H) * VIS3D / (F * 1.02)) * ((DIST3D - zMax - F * r) / DIST3D);
  // O dolly: o objeto chega do fundo e vem devagar para a lente durante a batida.
  const z = -3.5 * (1 - Math.min(1, k)) + zMax * Math.min(1, tl / dur);
  const esc = 0.55 + 0.45 * k;
  return (
    <div style={{ position: 'absolute', left: 0, top: 0, width: W, height: H, opacity: (slot.op != null ? slot.op : 1) * Math.min(1, tl / 0.3) * (1 - sai) }}>
      <ThreeCanvas width={W} height={H} camera={{ position: [0, 0, DIST3D], fov: FOV3D }} gl={{ antialias: true, preserveDrawingBuffer: true, alpha: true }}>
        <Ambiente forca={0.9} />
        <ambientLight intensity={0.3} />
        <directionalLight position={[3, 4, 6]} intensity={2.6} />
        <directionalLight position={[-5, -1, -3]} intensity={3.2} color={cor} />
        <pointLight position={[0, 0, 5]} intensity={0.6} />
        <group position={[wx, wy, z]} scale={[esc, esc, esc]}>
          {h.tipo === 'fita3d' ? <Fita r={r} cor={cor} cor2={cor2} sem={sem} tl={tl} /> : <Globo r={r} cor={cor} cor2={cor2} sem={sem} tl={tl} />}
        </group>
      </ThreeCanvas>
    </div>
  );
}

export const TRES = { produto3d: Produto3D, icone3d: Icone3D };
