/* kit/mundo.jsx — o MUNDO do vídeo 3D em blocos: o lugar, as peças, os bichos e as coisas.
 *
 * O voxel.jsx desenha a pessoa e o mascote e decide a câmera; aqui mora tudo
 * o que muda de um vídeo para o outro (mundo.js decide, isto só desenha):
 *  - Ceu, Chao, Mar e a luz de cada lugar (praia, cidade, espaço, cozinha…);
 *  - Peca: as peças de blocos do catálogo (palmeira, prédio, carro, foguete…);
 *  - IconeEmBlocos: QUALQUER coisa da biblioteca de ícones 3D, transformada em
 *    blocos na hora (o PNG vira uma grade de cubos com volume) — é o que deixa
 *    a cena montar o que não está no catálogo;
 *  - Animal: as espécies montadas de blocos, com boca que segue a voz.
 * Tudo é geometria do three.js; o único arquivo de fora é o PNG do ícone. */
import React, { useMemo, useLayoutEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { delayRender, continueRender, staticFile } from 'remotion';
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';

const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
const hash = (n) => { const s = Math.sin(n * 127.1 + 311.7) * 43758.5453; return s - Math.floor(s); };
const escurecer = (c, k) => new THREE.Color(c).multiplyScalar(k).getStyle();
const misturar = (a, b, k) => new THREE.Color(a).lerp(new THREE.Color(b), k).getStyle();

const GEOS = new Map();
function geo(w, h, d, r) {
  const raio = Math.min(r, w / 2 - 0.001, h / 2 - 0.001, d / 2 - 0.001);
  const k = `${w}|${h}|${d}|${raio}`;
  if (!GEOS.has(k)) GEOS.set(k, new RoundedBoxGeometry(w, h, d, 2, Math.max(0.001, raio)));
  return GEOS.get(k);
}
export const B = ({ s, p = [0, 0, 0], rot, c, r = 0.03, rough = 0.7, metal = 0, e, ei = 1, sombra = true, op }) => (
  <mesh position={p} rotation={rot} geometry={geo(s[0], s[1], s[2], r)} castShadow={sombra} receiveShadow>
    <meshStandardMaterial color={c} roughness={rough} metalness={metal} {...(e ? { emissive: e, emissiveIntensity: ei } : {})} {...(op != null ? { transparent: true, opacity: op } : {})} />
  </mesh>
);
const Olho = ({ p, s = 0.05, pisca = 0 }) => (
  <group position={p}>
    <mesh><boxGeometry args={[s, s * (1 - pisca * 0.85), 0.012]} /><meshStandardMaterial color="#141016" roughness={0.6} /></mesh>
    <mesh position={[s * 0.22, s * 0.22, 0.004]}><boxGeometry args={[s * 0.3, s * 0.3 * (1 - pisca), 0.01]} /><meshStandardMaterial color="#ffffff" roughness={0.5} /></mesh>
  </group>
);

/* Muitas caixas iguais numa chamada só (o chão, as estrelas, o ícone em blocos). */
function Instancias({ itens, w = 1, h = 1, d = 1, r = 0, rough = 0.8, sombra = false, emissivo = 0 }) {
  const ref = useRef();
  const g = useMemo(() => (r > 0 ? new RoundedBoxGeometry(w, h, d, 2, r) : new THREE.BoxGeometry(w, h, d)), [w, h, d, r]);
  useLayoutEffect(() => {
    const m = ref.current;
    if (!m) return;
    const o = new THREE.Object3D(), cor = new THREE.Color();
    itens.forEach((it, i) => {
      o.position.set(it.p[0], it.p[1], it.p[2]);
      o.scale.set(...(it.s || [1, 1, 1]));
      o.rotation.set(0, it.giro || 0, 0);
      o.updateMatrix();
      m.setMatrixAt(i, o.matrix);
      m.setColorAt(i, cor.set(it.c));
    });
    m.instanceMatrix.needsUpdate = true;
    if (m.instanceColor) m.instanceColor.needsUpdate = true;
  }, [itens]);
  if (!itens.length) return null;
  return (
    <instancedMesh ref={ref} args={[g, undefined, itens.length]} castShadow={sombra} receiveShadow>
      <meshStandardMaterial roughness={rough} {...(emissivo ? { emissive: '#ffffff', emissiveIntensity: emissivo, toneMapped: false } : {})} />
    </instancedMesh>
  );
}

/* ── O CÉU: uma cúpula com o degradê do lugar, e sol, lua e estrelas ───── */
function texturaDegrade(alto, horizonte) {
  const cv = document.createElement('canvas');
  cv.width = 4; cv.height = 256;
  const g = cv.getContext('2d');
  const gr = g.createLinearGradient(0, 0, 0, 256);
  gr.addColorStop(0, alto); gr.addColorStop(0.42, misturar(alto, horizonte, 0.55)); gr.addColorStop(0.5, horizonte); gr.addColorStop(1, horizonte);
  g.fillStyle = gr; g.fillRect(0, 0, 4, 256);
  const tx = new THREE.CanvasTexture(cv);
  tx.colorSpace = THREE.SRGBColorSpace;
  return tx;
}
function Ceu({ m, t }) {
  const tx = useMemo(() => texturaDegrade(m.ceu[0], m.ceu[1]), [m.ceu[0], m.ceu[1]]);
  const estrelas = useMemo(() => {
    if (!m.estrelas) return [];
    return Array.from({ length: 260 }, (_, i) => {
      const az = hash(i * 3.1) * Math.PI * 2, el = 0.08 + hash(i * 7.7) * 1.2, R = 42;
      const s = 0.06 + hash(i * 1.3) * 0.12;
      return { p: [Math.cos(az) * Math.cos(el) * R, Math.sin(el) * R, Math.sin(az) * Math.cos(el) * R], s: [s, s, s], c: hash(i) > 0.8 ? '#ffd9a8' : '#ffffff' };
    });
  }, [m.estrelas]);
  const solY = m.solBaixo ? 3.2 : 14, solCor = m.solBaixo ? '#ffb070' : '#fff6d8';
  return (
    <group>
      <mesh scale={[-1, 1, 1]}>
        <sphereGeometry args={[46, 32, 24]} />
        <meshBasicMaterial map={tx} side={THREE.BackSide} fog={false} depthWrite={false} toneMapped={false} />
      </mesh>
      {estrelas.length > 0 && <Instancias itens={estrelas} w={1} h={1} d={1} emissivo={0.9} />}
      {/* o sol e a lua são blocos que brilham: o bloom faz o halo */}
      {m.sol && <B s={[2.6, 2.6, 0.4]} p={[-9, solY, -40]} c={solCor} e={solCor} ei={m.solBaixo ? 2.2 : 1.6} r={0.3} sombra={false} />}
      {m.lua && <B s={[2, 2, 0.3]} p={[8, 12, -40]} c="#e9ecf5" e="#dfe6ff" ei={1.1} r={0.25} sombra={false} />}
      {!m.subaquatico && !m.estrelas && [0, 1, 2, 3].map((i) => (
        <group key={i} position={[(-14 + i * 9 + ((t * 0.25 + i * 3) % 6)) , 9 + hash(i) * 4, -30 - hash(i + 2) * 6]}>
          <B s={[3.4, 0.8, 1.2]} c="#ffffff" r={0.3} rough={1} sombra={false} op={m.hora === 'noite' ? 0.25 : 0.92} />
          <B s={[2, 0.9, 1.1]} p={[0.5, 0.5, 0]} c="#ffffff" r={0.3} rough={1} sombra={false} op={m.hora === 'noite' ? 0.25 : 0.92} />
        </group>
      ))}
    </group>
  );
}

/* ── O CHÃO: ladrilhos de bloco, com a cor e o relevo do lugar ─────────── */
function Chao({ m }) {
  const { tipo, cor, cor2 } = m.chao;
  const itens = useMemo(() => {
    const out = [];
    const passo = tipo === 'ladrilho' ? 0.92 : tipo === 'madeira' ? 0.5 : 1;
    const nx = m.dentro ? 12 : 26, nz = m.dentro ? 8 : 26;
    for (let i = -nx; i <= nx; i++) {
      for (let j = -nz; j <= 6; j++) {
        const x = i * passo + (tipo === 'ladrilho' && j % 2 ? passo / 2 : 0), z = j * (tipo === 'madeira' ? 1.6 : passo);
        if (m.agua && z < -4.6) continue;
        if (m.dentro && z < -3.6) continue;
        const k = hash(i * 13.1 + j * 7.3);
        const natural = tipo === 'grama' || tipo === 'areia' || tipo === 'neve' || tipo === 'lua';
        const alto = natural ? 0.12 + k * 0.08 : 0.1;
        // O longe vai assentando: o relevo some no horizonte e não pisca.
        let c = k > 0.5 ? cor : cor2;
        if (tipo === 'grama' && k > 0.93) c = misturar(cor, '#e8e070', 0.35);
        if (tipo === 'asfalto' && Math.abs(x) < 0.12 && j % 2 === 0 && z < -1) c = '#e8e4d8';
        if (tipo === 'lua' && k > 0.9) c = escurecer(cor, 0.78);
        out.push({ p: [x, -alto / 2, z], s: [tipo === 'madeira' ? 0.48 / passo : 0.98, alto, tipo === 'madeira' ? 1.56 / passo : 0.98], c });
      }
    }
    return out;
  }, [tipo, cor, cor2, m.agua, m.dentro]);
  const passo = tipo === 'ladrilho' ? 0.92 : tipo === 'madeira' ? 0.5 : 1;
  return (
    <group>
      <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.2, 0]} receiveShadow>
        <planeGeometry args={[120, 120]} /><meshStandardMaterial color={escurecer(cor2, 0.8)} roughness={0.95} />
      </mesh>
      <Instancias itens={itens} w={passo} h={1} d={passo} rough={tipo === 'ladrilho' ? 0.45 : tipo === 'neve' ? 0.9 : 0.85} />
    </group>
  );
}

/* ── O MAR (praia): a água e as ondas de bloco que vão e voltam ─────────── */
function Mar({ m, t }) {
  const ondas = useMemo(() => Array.from({ length: 60 }, (_, i) => ({ x: (i % 20 - 10) * 2.2 + hash(i) * 1.2, z: -5.6 - Math.floor(i / 20) * 2.6 - hash(i + 5), k: hash(i * 3) })), []);
  return (
    <group>
      <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.12, -26]}>
        <planeGeometry args={[140, 44]} /><meshStandardMaterial color={m.agua} roughness={0.15} metalness={0.2} />
      </mesh>
      {/* a espuma da beira, que avança e recua */}
      <B s={[60, 0.06, 0.5]} p={[0, -0.06, -4.75 + Math.sin(t * 0.9) * 0.18]} c="#f4fbff" r={0.02} rough={0.9} sombra={false} />
      {ondas.map((o, i) => (
        <B key={i} s={[1.4, 0.08, 0.3]} p={[o.x + Math.sin(t * 0.6 + o.k * 6) * 0.3, -0.05 + Math.sin(t * 1.6 + o.k * 9) * 0.04, o.z]} c={misturar(m.agua, '#ffffff', 0.45)} r={0.02} rough={0.5} sombra={false} />
      ))}
    </group>
  );
}

/* ── O SET FECHADO (escritório, sala, cozinha, escola, palco): a parede ─── */
function Paredes({ m }) {
  return (
    <group>
      <B s={[30, 6, 0.3]} p={[0, 2.9, -3.75]} c={m.parede} r={0.02} rough={0.95} sombra={false} />
      <B s={[30, 0.18, 0.06]} p={[0, 0.09, -3.58]} c={escurecer(m.parede, 0.7)} r={0.01} sombra={false} />
      {/* a janela de luz, que dá profundidade ao set fechado */}
      {!m.holofotes && <group position={[3.4, 2.3, -3.58]}>
        <B s={[1.9, 1.5, 0.06]} c="#cfe6ff" e="#cfe6ff" ei={0.6} r={0.02} sombra={false} />
        <B s={[0.08, 1.5, 0.08]} c={escurecer(m.parede, 0.6)} r={0.01} sombra={false} />
        <B s={[1.9, 0.08, 0.08]} c={escurecer(m.parede, 0.6)} r={0.01} sombra={false} />
      </group>}
    </group>
  );
}

/* ── AS PEÇAS DO CATÁLOGO ───────────────────────────────────────────────
   Cada uma é um punhado de blocos com a proporção certa. A base fica no
   chão (y = 0); a escala e o giro vêm do mundo.js. */
const VERDE = '#3f8f3a', VERDE2 = '#2f7a2e', TRONCO = '#7a5232', CINZA = '#8b8f98';
function Arvore({ k = 0, copa = VERDE }) {
  return (
    <group>
      <B s={[0.32, 1.4, 0.32]} p={[0, 0.7, 0]} c={TRONCO} r={0.04} />
      <B s={[1.5, 1.1, 1.5]} p={[0, 1.85, 0]} c={copa} r={0.12} rough={0.9} />
      <B s={[1.05, 0.8, 1.05]} p={[0.1, 2.6, -0.05]} c={misturar(copa, '#9fd86a', 0.2 + k * 0.2)} r={0.1} rough={0.9} />
    </group>
  );
}
function Pinheiro({ neve }) {
  return (
    <group>
      <B s={[0.26, 0.7, 0.26]} p={[0, 0.35, 0]} c={TRONCO} r={0.04} />
      {[0, 1, 2, 3].map((i) => (
        <group key={i}>
          <B s={[1.6 - i * 0.36, 0.55, 1.6 - i * 0.36]} p={[0, 0.9 + i * 0.55, 0]} c={i % 2 ? VERDE2 : '#2b6a32'} r={0.05} rough={0.9} />
          {neve && <B s={[1.5 - i * 0.36, 0.12, 1.5 - i * 0.36]} p={[0, 1.2 + i * 0.55, 0]} c="#f6faff" r={0.04} rough={0.9} />}
        </group>
      ))}
    </group>
  );
}
function Palmeira({ t, k = 0 }) {
  const balanco = Math.sin(t * 0.8 + k * 5) * 0.05;
  return (
    <group>
      {[0, 1, 2, 3, 4, 5].map((i) => <B key={i} s={[0.3 - i * 0.015, 0.5, 0.3 - i * 0.015]} p={[i * i * 0.012, 0.25 + i * 0.5, 0]} c={i % 2 ? '#8a6238' : '#9b7044'} r={0.04} />)}
      <group position={[0.36, 3.05, 0]} rotation={[0, 0, balanco]}>
        {[0, 1, 2, 3, 4, 5].map((i) => {
          const a = (i / 6) * Math.PI * 2;
          return <B key={i} s={[1.6, 0.1, 0.42]} p={[Math.cos(a) * 0.75, -0.18, Math.sin(a) * 0.75]} rot={[0, -a, -0.35]} c={i % 2 ? '#3f9a3a' : '#2f8a34'} r={0.04} rough={0.85} />;
        })}
        <B s={[0.24, 0.24, 0.24]} p={[0.05, -0.2, 0.1]} c="#5b3b1c" r={0.06} />
        <B s={[0.24, 0.24, 0.24]} p={[-0.1, -0.22, -0.08]} c="#5b3b1c" r={0.06} />
      </group>
    </group>
  );
}
function Predio({ k = 0, noite }) {
  const andares = 4 + Math.floor(k * 6), cor = ['#c9ced8', '#9aa6b8', '#d8c8b0', '#7f8da3', '#b9a99a'][Math.floor(k * 5)];
  const janelas = [];
  for (let a = 0; a < andares; a++) for (let j = 0; j < 3; j++) {
    const acesa = hash(a * 7 + j * 3 + k * 50) > (noite ? 0.45 : 1.1);
    janelas.push(<B key={a + '-' + j} s={[0.36, 0.42, 0.04]} p={[(j - 1) * 0.55, 0.65 + a * 0.8, 0.93]} c={acesa ? '#ffd98a' : '#3b4a63'} e={acesa ? '#ffcc70' : undefined} ei={1.2} r={0.01} sombra={false} />);
  }
  return (
    <group>
      <B s={[1.9, andares * 0.8 + 0.4, 1.8]} p={[0, (andares * 0.8 + 0.4) / 2, 0]} c={cor} r={0.04} rough={0.85} />
      {janelas}
    </group>
  );
}
function Casa({ cor = '#e8d6b8', telhado = '#b5452f' }) {
  return (
    <group>
      <B s={[2.2, 1.6, 1.8]} p={[0, 0.8, 0]} c={cor} r={0.04} />
      <B s={[2.5, 0.35, 2.1]} p={[0, 1.78, 0]} c={telhado} r={0.04} />
      <B s={[1.7, 0.35, 1.7]} p={[0, 2.12, 0]} c={telhado} r={0.04} />
      <B s={[0.5, 0.9, 0.06]} p={[0, 0.45, 0.91]} c="#6b4423" r={0.02} />
      <B s={[0.45, 0.4, 0.05]} p={[0.65, 1, 0.91]} c="#cfe6ff" r={0.02} />
    </group>
  );
}
function Carro({ k = 0 }) {
  const cor = ['#d0452f', '#2f4cdd', '#f1b434', '#1f8a5b', '#e8e2d6'][Math.floor(k * 5)];
  return (
    <group rotation={[0, Math.PI / 2, 0]}>
      <B s={[1.9, 0.5, 0.95]} p={[0, 0.45, 0]} c={cor} r={0.1} rough={0.35} metal={0.2} />
      <B s={[1.05, 0.42, 0.85]} p={[-0.1, 0.9, 0]} c={cor} r={0.1} rough={0.35} metal={0.2} />
      <B s={[0.95, 0.32, 0.87]} p={[-0.1, 0.92, 0]} c="#2b3a4f" r={0.06} rough={0.2} />
      {[[-0.6, 0.47], [0.6, 0.47], [-0.6, -0.47], [0.6, -0.47]].map(([x, z], i) => <B key={i} s={[0.36, 0.36, 0.14]} p={[x, 0.2, z]} c="#16161a" r={0.06} />)}
      <B s={[0.06, 0.12, 0.2]} p={[0.96, 0.5, 0.3]} c="#fff6d0" e="#fff1c0" ei={0.8} r={0.02} sombra={false} />
      <B s={[0.06, 0.12, 0.2]} p={[0.96, 0.5, -0.3]} c="#fff6d0" e="#fff1c0" ei={0.8} r={0.02} sombra={false} />
    </group>
  );
}
function Poste({ noite }) {
  return (
    <group>
      <B s={[0.14, 3, 0.14]} p={[0, 1.5, 0]} c="#2b2d33" r={0.03} />
      <B s={[0.6, 0.1, 0.14]} p={[0.25, 3, 0]} c="#2b2d33" r={0.03} />
      <B s={[0.3, 0.14, 0.24]} p={[0.48, 2.92, 0]} c="#fff1c0" e="#ffd98a" ei={noite ? 3 : 0.4} r={0.03} sombra={false} />
      {noite && <pointLight position={[0.48, 2.6, 0]} intensity={6} distance={6} color="#ffd98a" />}
    </group>
  );
}
function GuardaSol({ k = 0 }) {
  const c1 = ['#e5484d', '#2f80ed', '#f1b434'][Math.floor(k * 3)];
  return (
    <group>
      <B s={[0.08, 2, 0.08]} p={[0, 1, 0]} c="#e8e2d6" r={0.02} />
      {[0, 1, 2, 3, 4, 5, 6, 7].map((i) => {
        const a = (i / 8) * Math.PI * 2;
        return <B key={i} s={[1.1, 0.08, 0.5]} p={[Math.cos(a) * 0.5, 1.92, Math.sin(a) * 0.5]} rot={[0, -a, -0.22]} c={i % 2 ? c1 : '#f6f2ea'} r={0.02} />;
      })}
    </group>
  );
}
function CadeiraDePraia() {
  return (
    <group>
      <B s={[0.6, 0.06, 0.9]} p={[0, 0.3, 0]} c="#2f80ed" r={0.02} />
      <B s={[0.6, 0.06, 0.7]} p={[0, 0.55, -0.55]} rot={[0.9, 0, 0]} c="#f6f2ea" r={0.02} />
      {[-0.27, 0.27].map((x, i) => <B key={i} s={[0.05, 0.3, 0.05]} p={[x, 0.15, 0.3]} c="#c9c2b4" r={0.01} />)}
    </group>
  );
}
function CasteloDeAreia() {
  const a = '#d9be86';
  return (
    <group>
      <B s={[0.9, 0.35, 0.9]} p={[0, 0.17, 0]} c={a} r={0.04} rough={1} />
      {[[-0.35, -0.35], [0.35, -0.35], [-0.35, 0.35], [0.35, 0.35]].map(([x, z], i) => <B key={i} s={[0.26, 0.6, 0.26]} p={[x, 0.3, z]} c={a} r={0.04} rough={1} />)}
      <B s={[0.36, 0.45, 0.36]} p={[0, 0.55, 0]} c={a} r={0.04} rough={1} />
      <B s={[0.03, 0.3, 0.03]} p={[0, 0.9, 0]} c="#6b4423" r={0.01} />
      <B s={[0.2, 0.12, 0.02]} p={[0.1, 1, 0]} c="#e5484d" r={0.01} />
    </group>
  );
}
function Pedra({ k = 0, cor = CINZA }) {
  return (
    <group>
      <B s={[0.9 + k * 0.4, 0.55, 0.75]} p={[0, 0.27, 0]} c={cor} r={0.12} rough={1} />
      <B s={[0.5, 0.35, 0.5]} p={[0.25, 0.6, 0.05]} c={escurecer(cor, 0.9)} r={0.1} rough={1} />
    </group>
  );
}
function Cacto() {
  const v = '#3f8f4a';
  return (
    <group>
      <B s={[0.36, 2, 0.36]} p={[0, 1, 0]} c={v} r={0.08} />
      <B s={[0.5, 0.24, 0.24]} p={[-0.38, 0.9, 0]} c={v} r={0.06} />
      <B s={[0.24, 0.6, 0.24]} p={[-0.55, 1.25, 0]} c={v} r={0.06} />
      <B s={[0.45, 0.22, 0.22]} p={[0.35, 1.3, 0]} c={v} r={0.06} />
      <B s={[0.22, 0.5, 0.22]} p={[0.5, 1.6, 0]} c={v} r={0.06} />
    </group>
  );
}
function Montanha({ neve, cor = '#7b8b6a' }) {
  return (
    <group scale={[2.6, 2.6, 2.6]}>
      {[0, 1, 2, 3].map((i) => <B key={i} s={[3 - i * 0.7, 0.7, 3 - i * 0.7]} p={[0, 0.35 + i * 0.7, 0]} c={i === 3 && neve !== false ? '#f6faff' : misturar(cor, '#5c6b54', i * 0.15)} r={0.08} rough={1} sombra={false} />)}
    </group>
  );
}
function Mesa({ cor = '#8a5a3c' }) {
  return (
    <group>
      <B s={[1.4, 0.08, 0.8]} p={[0, 0.76, 0]} c={cor} r={0.02} />
      {[[-0.62, -0.32], [0.62, -0.32], [-0.62, 0.32], [0.62, 0.32]].map(([x, z], i) => <B key={i} s={[0.07, 0.74, 0.07]} p={[x, 0.37, z]} c={escurecer(cor, 0.8)} r={0.01} />)}
    </group>
  );
}
function Cadeira({ cor = '#2b2d33' }) {
  return (
    <group>
      <B s={[0.5, 0.07, 0.5]} p={[0, 0.48, 0]} c={cor} r={0.02} />
      <B s={[0.5, 0.55, 0.07]} p={[0, 0.78, -0.22]} c={cor} r={0.02} />
      {[[-0.2, -0.2], [0.2, -0.2], [-0.2, 0.2], [0.2, 0.2]].map(([x, z], i) => <B key={i} s={[0.05, 0.46, 0.05]} p={[x, 0.23, z]} c={escurecer(cor, 0.8)} r={0.01} />)}
    </group>
  );
}
function Sofa({ cor = '#4f6dd8' }) {
  return (
    <group>
      <B s={[2, 0.45, 0.85]} p={[0, 0.3, 0]} c={cor} r={0.08} rough={0.95} />
      <B s={[2, 0.6, 0.25]} p={[0, 0.75, -0.32]} c={cor} r={0.08} rough={0.95} />
      {[-0.95, 0.95].map((x, i) => <B key={i} s={[0.22, 0.6, 0.85]} p={[x, 0.45, 0]} c={escurecer(cor, 0.9)} r={0.08} rough={0.95} />)}
    </group>
  );
}
function Planta() {
  return (
    <group>
      <B s={[0.45, 0.5, 0.45]} p={[0, 0.25, 0]} c="#c9784a" r={0.05} />
      {[0, 1, 2, 3, 4].map((i) => <B key={i} s={[0.16, 0.7 + hash(i) * 0.3, 0.16]} p={[Math.cos(i * 1.3) * 0.13, 0.85, Math.sin(i * 1.3) * 0.13]} rot={[Math.sin(i) * 0.3, 0, Math.cos(i) * 0.3]} c={i % 2 ? VERDE : VERDE2} r={0.04} />)}
    </group>
  );
}
function Estante({ k = 0 }) {
  const livros = [];
  for (let a = 0; a < 3; a++) for (let i = 0; i < 6; i++) {
    const h = 0.32 + hash(a * 9 + i + k * 20) * 0.12;
    livros.push(<B key={a + '-' + i} s={[0.13, h, 0.3]} p={[-0.45 + i * 0.18, 0.12 + a * 0.62 + h / 2, 0.02]} c={['#c2452f', '#2f4cdd', '#1f8a5b', '#d08a1e', '#7b5cff', '#e8e2d6'][(a * 2 + i) % 6]} r={0.01} sombra={false} />);
  }
  return (
    <group>
      <B s={[1.3, 1.95, 0.4]} p={[0, 0.97, -0.02]} c="#6b4a2f" r={0.03} />
      {livros}
    </group>
  );
}
function Computador() {
  return (
    <group>
      <Mesa />
      <B s={[0.75, 0.48, 0.05]} p={[0, 1.18, -0.15]} c="#16181f" r={0.02} />
      <B s={[0.68, 0.4, 0.02]} p={[0, 1.18, -0.12]} c="#3d6fff" e="#3d6fff" ei={0.7} r={0.01} sombra={false} />
      <B s={[0.08, 0.2, 0.08]} p={[0, 0.88, -0.15]} c="#16181f" r={0.01} />
      <B s={[0.5, 0.03, 0.18]} p={[0, 0.81, 0.15]} c="#2b2d33" r={0.01} />
    </group>
  );
}
function Luminaria() {
  return (
    <group>
      <B s={[0.36, 0.06, 0.36]} p={[0, 0.03, 0]} c="#2b2d33" r={0.02} />
      <B s={[0.06, 1.6, 0.06]} p={[0, 0.8, 0]} c="#2b2d33" r={0.01} />
      <B s={[0.5, 0.36, 0.5]} p={[0, 1.7, 0]} c="#ffe8b8" e="#ffd98a" ei={1.4} r={0.06} sombra={false} />
      <pointLight position={[0, 1.6, 0.3]} intensity={3} distance={4} color="#ffd98a" />
    </group>
  );
}
function Quadro({ k = 0, lousa }) {
  return (
    <group position={[0, lousa ? 1.6 : 2.1, 0]}>
      <B s={[lousa ? 3 : 1.2, lousa ? 1.5 : 0.85, 0.06]} c={lousa ? '#5b3d22' : '#2b2d33'} r={0.02} sombra={false} />
      <B s={[lousa ? 2.8 : 1.05, lousa ? 1.3 : 0.7, 0.02]} p={[0, 0, 0.03]} c={lousa ? '#24453a' : ['#f1b434', '#2f80ed', '#e5484d'][Math.floor(k * 3)]} r={0.01} sombra={false} />
    </group>
  );
}
function Balcao() {
  return (
    <group>
      <B s={[2.6, 1, 0.8]} p={[0, 0.5, 0]} c="#8a5a3c" r={0.03} />
      <B s={[2.7, 0.08, 0.9]} p={[0, 1.04, 0]} c="#e8e2d6" r={0.02} />
      {[-0.8, 0, 0.8].map((x, i) => <B key={i} s={[0.32, 0.25, 0.32]} p={[x, 1.2, 0]} c={['#d9a066', '#e8c48a', '#b5763e'][i]} r={0.08} />)}
    </group>
  );
}
function Cerca() {
  return (
    <group>
      {[-0.9, -0.3, 0.3, 0.9].map((x, i) => <B key={i} s={[0.12, 0.9, 0.12]} p={[x, 0.45, 0]} c="#e8e2d6" r={0.02} />)}
      <B s={[2.1, 0.1, 0.06]} p={[0, 0.6, 0.06]} c="#e8e2d6" r={0.02} />
      <B s={[2.1, 0.1, 0.06]} p={[0, 0.3, 0.06]} c="#e8e2d6" r={0.02} />
    </group>
  );
}
function Celeiro() { return <group scale={[1.2, 1.2, 1.2]}><Casa cor="#b5352a" telhado="#e8e2d6" /></group>; }
function Feno() { return <B s={[0.9, 0.6, 0.6]} p={[0, 0.3, 0]} c="#e2c35a" r={0.08} rough={1} />; }
function BonecoDeNeve() {
  return (
    <group>
      <B s={[0.9, 0.8, 0.9]} p={[0, 0.4, 0]} c="#f8fbff" r={0.25} rough={0.9} />
      <B s={[0.65, 0.6, 0.65]} p={[0, 1.05, 0]} c="#f8fbff" r={0.2} rough={0.9} />
      <B s={[0.45, 0.45, 0.45]} p={[0, 1.55, 0]} c="#f8fbff" r={0.15} rough={0.9} />
      <B s={[0.07, 0.07, 0.28]} p={[0, 1.55, 0.32]} c="#f97316" r={0.02} />
      <Olho p={[-0.1, 1.64, 0.23]} s={0.06} /><Olho p={[0.1, 1.64, 0.23]} s={0.06} />
      <B s={[0.55, 0.1, 0.55]} p={[0, 1.79, 0]} c="#16161a" r={0.02} /><B s={[0.36, 0.3, 0.36]} p={[0, 1.96, 0]} c="#16161a" r={0.02} />
    </group>
  );
}
function Foguete({ t }) {
  return (
    <group position={[0, Math.sin(t * 0.7) * 0.05, 0]}>
      <B s={[0.8, 2.6, 0.8]} p={[0, 1.6, 0]} c="#eef0f4" r={0.2} rough={0.4} metal={0.2} />
      <B s={[0.56, 0.7, 0.56]} p={[0, 3.2, 0]} c="#e5484d" r={0.2} />
      <B s={[0.36, 0.36, 0.06]} p={[0, 2.3, 0.41]} c="#3d6fff" e="#3d6fff" ei={0.6} r={0.08} sombra={false} />
      {[0, 1, 2, 3].map((i) => <B key={i} s={[0.1, 0.8, 0.6]} p={[Math.cos(i * Math.PI / 2) * 0.48, 0.55, Math.sin(i * Math.PI / 2) * 0.48]} rot={[0, -i * Math.PI / 2, 0]} c="#e5484d" r={0.03} />)}
    </group>
  );
}
function Planeta({ k = 0, t }) {
  const cor = ['#f1b434', '#7b5cff', '#2fa4ff', '#e5484d'][Math.floor(k * 4)];
  return (
    <group position={[0, 4 + k * 3, 0]} rotation={[0.3, t * 0.15, 0.2]}>
      <B s={[2.2, 2.2, 2.2]} c={cor} r={0.5} rough={0.8} />
      <B s={[3.6, 0.08, 3.6]} c={misturar(cor, '#ffffff', 0.4)} r={0.04} rough={0.6} sombra={false} />
    </group>
  );
}
function Cratera() {
  return (
    <group>
      {[0, 1, 2, 3, 4, 5].map((i) => { const a = (i / 6) * Math.PI * 2; return <B key={i} s={[0.6, 0.18, 0.4]} p={[Math.cos(a) * 0.7, 0.09, Math.sin(a) * 0.7]} rot={[0, -a, 0]} c="#8a8a96" r={0.04} rough={1} />; })}
    </group>
  );
}
function Alga({ t, k = 0 }) {
  return (
    <group>
      {[0, 1, 2, 3, 4, 5].map((i) => <B key={i} s={[0.16, 0.42, 0.16]} p={[Math.sin(t * 1.2 + i * 0.6 + k * 5) * 0.06 * i, 0.21 + i * 0.4, 0]} c={i % 2 ? '#2f9a5a' : '#3fae6a'} r={0.05} />)}
    </group>
  );
}
function Coral({ k = 0 }) {
  const cor = ['#ff6f91', '#ff9f43', '#c56cf0'][Math.floor(k * 3)];
  return (
    <group>
      {[[0, 0.3, 0, 0.6], [0.25, 0.55, 0.1, 0.5], [-0.2, 0.6, -0.05, 0.6], [0.05, 0.9, 0, 0.4]].map(([x, y, z, h], i) => <B key={i} s={[0.2, h, 0.2]} p={[x, y, z]} c={cor} r={0.06} rough={0.9} />)}
    </group>
  );
}
function Bau() {
  return (
    <group>
      <B s={[0.9, 0.55, 0.6]} p={[0, 0.27, 0]} c="#8a5a2c" r={0.04} />
      <B s={[0.92, 0.25, 0.62]} p={[0, 0.66, -0.05]} rot={[-0.35, 0, 0]} c="#9b6a36" r={0.04} />
      <B s={[0.7, 0.12, 0.4]} p={[0, 0.58, 0.02]} c="#f5c542" e="#f5c542" ei={0.5} r={0.04} sombra={false} />
    </group>
  );
}
function Cogumelo() {
  return (
    <group>
      <B s={[0.22, 0.45, 0.22]} p={[0, 0.22, 0]} c="#f2e6d0" r={0.05} />
      <B s={[0.7, 0.3, 0.7]} p={[0, 0.55, 0]} c="#d6332f" r={0.12} />
      {[[0.15, 0.2], [-0.18, -0.1], [0.05, -0.22]].map(([x, z], i) => <B key={i} s={[0.1, 0.04, 0.1]} p={[x, 0.71, z]} c="#ffffff" r={0.02} />)}
    </group>
  );
}
function Fogueira({ t }) {
  return (
    <group>
      {[0, 1, 2, 3].map((i) => <B key={i} s={[0.8, 0.14, 0.14]} p={[0, 0.07, 0]} rot={[0, i * Math.PI / 4, 0]} c="#6b4423" r={0.03} />)}
      {[0, 1, 2].map((i) => <B key={'f' + i} s={[0.3 - i * 0.07, 0.35 + Math.sin(t * 9 + i * 2) * 0.08, 0.3 - i * 0.07]} p={[0, 0.3 + i * 0.2, 0]} c={['#ff7a1a', '#ffb31a', '#ffe08a'][i]} e={['#ff5a00', '#ff9a00', '#ffd060'][i]} ei={2.4} r={0.04} sombra={false} />)}
      <pointLight position={[0, 0.8, 0]} intensity={8 + Math.sin(t * 11) * 2} distance={6} color="#ff9a3a" />
    </group>
  );
}
function Barraca() {
  return (
    <group>
      <B s={[1.7, 0.1, 1.6]} p={[0, 0.05, 0]} c="#2f6a3a" r={0.02} />
      <B s={[1.2, 0.08, 1.6]} p={[-0.38, 0.62, 0]} rot={[0, 0, 0.95]} c="#f08c2a" r={0.02} />
      <B s={[1.2, 0.08, 1.6]} p={[0.38, 0.62, 0]} rot={[0, 0, -0.95]} c="#e27a1a" r={0.02} />
    </group>
  );
}
function Barco() {
  return (
    <group>
      <B s={[2.2, 0.4, 0.9]} p={[0, 0.2, 0]} c="#e8e2d6" r={0.12} />
      <B s={[0.08, 1.8, 0.08]} p={[0, 1.2, 0]} c="#6b4423" r={0.02} />
      <B s={[0.9, 1.3, 0.04]} p={[0.48, 1.3, 0]} c="#ffffff" r={0.02} />
    </group>
  );
}
function CaixaDeSom() {
  return (
    <group>
      <B s={[0.8, 1.4, 0.6]} p={[0, 0.7, 0]} c="#16161a" r={0.04} />
      <B s={[0.5, 0.5, 0.04]} p={[0, 0.95, 0.31]} c="#2b2d33" r={0.2} />
      <B s={[0.3, 0.3, 0.04]} p={[0, 0.35, 0.31]} c="#2b2d33" r={0.12} />
    </group>
  );
}
function Banco() {
  return (
    <group>
      <B s={[1.6, 0.08, 0.45]} p={[0, 0.45, 0]} c="#8a5a3c" r={0.02} />
      <B s={[1.6, 0.35, 0.06]} p={[0, 0.72, -0.2]} c="#8a5a3c" r={0.02} />
      {[-0.7, 0.7].map((x, i) => <B key={i} s={[0.08, 0.45, 0.4]} p={[x, 0.22, 0]} c="#2b2d33" r={0.02} />)}
    </group>
  );
}
function Flor({ k = 0 }) {
  const cor = ['#ff6f91', '#f1b434', '#c56cf0', '#ffffff', '#ff8a3a'][Math.floor(k * 5)];
  return (
    <group>
      {[0, 1, 2].map((i) => (
        <group key={i} position={[(i - 1) * 0.22, 0, (i % 2) * 0.15]}>
          <B s={[0.05, 0.35 + i * 0.05, 0.05]} p={[0, 0.18, 0]} c={VERDE2} r={0.01} />
          <B s={[0.16, 0.12, 0.16]} p={[0, 0.4 + i * 0.05, 0]} c={cor} r={0.04} />
          <B s={[0.06, 0.06, 0.06]} p={[0, 0.47 + i * 0.05, 0]} c="#f8d24a" r={0.02} />
        </group>
      ))}
    </group>
  );
}
function Tapete() { return <B s={[2.4, 0.03, 1.6]} p={[0, 0.02, 1.2]} c="#b5452f" r={0.01} sombra={false} />; }
function Nuvem() { return <group position={[0, 3.2, 0]}><B s={[1.8, 0.6, 0.8]} c="#ffffff" r={0.25} rough={1} /><B s={[1, 0.6, 0.7]} p={[0.3, 0.4, 0]} c="#ffffff" r={0.25} rough={1} /></group>; }

const PECAS = {
  arvore: (x) => <Arvore k={x.k} />, pinheiro: () => <Pinheiro />, 'pinheiro nevado': () => <Pinheiro neve />, palmeira: (x) => <Palmeira t={x.t} k={x.k} />,
  arbusto: () => <B s={[1, 0.7, 0.9]} p={[0, 0.35, 0]} c={VERDE} r={0.15} rough={0.95} />, flor: (x) => <Flor k={x.k} />, pedra: (x) => <Pedra k={x.k} cor={x.m.lugar === 'espaco' ? '#9a9aa6' : CINZA} />,
  cacto: () => <Cacto />, montanha: (x) => <Montanha neve={x.m.lugar !== 'deserto'} cor={x.m.lugar === 'deserto' ? '#c98a4a' : '#7b8b6a'} />, casa: () => <Casa />, predio: (x) => <Predio k={x.k} noite={x.m.hora === 'noite'} />,
  celeiro: () => <Celeiro />, poste: (x) => <Poste noite={x.m.hora === 'noite' || x.m.hora === 'por_do_sol'} />, carro: (x) => <Carro k={x.k} />, 'guarda-sol': (x) => <GuardaSol k={x.k} />,
  'cadeira de praia': () => <CadeiraDePraia />, 'castelo de areia': () => <CasteloDeAreia />, mesa: () => <Mesa />, cadeira: () => <Cadeira />, sofa: () => <Sofa />,
  planta: () => <Planta />, estante: (x) => <Estante k={x.k} />, computador: () => <Computador />, luminaria: () => <Luminaria />, quadro: (x) => <Quadro k={x.k} />,
  lousa: () => <Quadro lousa />, balcao: () => <Balcao />, tapete: () => <Tapete />, banco: () => <Banco />, cerca: () => <Cerca />, feno: () => <Feno />,
  'boneco de neve': () => <BonecoDeNeve />, foguete: (x) => <Foguete t={x.t} />, planeta: (x) => <Planeta k={x.k} t={x.t} />, cratera: () => <Cratera />,
  alga: (x) => <Alga t={x.t} k={x.k} />, coral: (x) => <Coral k={x.k} />, bau: () => <Bau />, cogumelo: () => <Cogumelo />, nuvem: () => <Nuvem />,
  fogueira: (x) => <Fogueira t={x.t} />, barraca: () => <Barraca />, barco: () => <Barco />, 'caixa de som': () => <CaixaDeSom />,
};

/* ── O ÍCONE EM BLOCOS ──────────────────────────────────────────────────
   O PNG do ícone 3D (colecao.js) é lido num canvas pequeno: cada pixel vira
   um cubo, e o miolo ganha mais camadas que a borda — o objeto fica com
   volume de verdade, e não um recorte chapado. É o que deixa "uma pizza",
   "um violão" ou "um dragão" entrarem na cena no mesmo visual de blocos. */
const LADO = 34;
function lerIcone(src, pronto) {
  const img = new Image();
  img.crossOrigin = 'anonymous';
  img.onload = () => {
    const cv = document.createElement('canvas');
    cv.width = LADO; cv.height = LADO;
    const g = cv.getContext('2d');
    g.drawImage(img, 0, 0, LADO, LADO);
    const px = g.getImageData(0, 0, LADO, LADO).data;
    const cheio = (x, y) => x >= 0 && y >= 0 && x < LADO && y < LADO && px[(y * LADO + x) * 4 + 3] > 110;
    // A distância até a borda (até 3): o miolo é mais grosso.
    const dist = (x, y) => { for (let d = 1; d <= 3; d++) if (!cheio(x - d, y) || !cheio(x + d, y) || !cheio(x, y - d) || !cheio(x, y + d)) return d; return 4; };
    const cubos = [];
    for (let y = 0; y < LADO; y++) for (let x = 0; x < LADO; x++) {
      if (!cheio(x, y)) continue;
      const i = (y * LADO + x) * 4;
      const c = `rgb(${px[i]},${px[i + 1]},${px[i + 2]})`;
      const camadas = dist(x, y);
      for (let z = -camadas + 1; z < camadas; z++) {
        const frente = z === camadas - 1;
        cubos.push({ x, y, z, c: frente ? c : escurecer(c, 0.82) });
      }
    }
    pronto(cubos);
  };
  img.onerror = () => pronto([]);
  img.src = staticFile(src);
}
/* Os ícones são lidos FORA do canvas 3D (useIcones, chamado pelo voxel.jsx
   na árvore normal do React): dentro do canvas o filho monta depois que o
   Remotion já decidiu que o quadro estava pronto, e o quadro saía sem o
   ícone. Aqui o render espera todos os PNGs virarem blocos. */
export function useIcones(srcs) {
  const chave = srcs.join('|');
  const [mapa, setMapa] = useState({});
  const [h] = useState(() => (srcs.length ? delayRender('ícones em blocos', { timeoutInMilliseconds: 60000 }) : null));
  useLayoutEffect(() => {
    if (!srcs.length) return;
    let falta = srcs.length;
    const out = {};
    for (const src of srcs) lerIcone(src, (c) => { out[src] = c; if (--falta === 0) setMapa(out); });
  }, [chave]);
  useLayoutEffect(() => {
    if (h != null && Object.keys(mapa).length === srcs.length) continueRender(h);
  }, [mapa]);
  return mapa;
}

export function IconeEmBlocos({ cubos, alto = 1.2 }) {
  const itens = useMemo(() => {
    if (!cubos) return [];
    const k = alto / LADO;
    return cubos.map((q) => ({ p: [(q.x - LADO / 2) * k, (LADO - q.y) * k, q.z * k], c: q.c }));
  }, [cubos, alto]);
  if (!itens.length) return null;
  const k = alto / LADO;
  return <Instancias itens={itens} w={k * 0.98} h={k * 0.98} d={k * 0.98} r={k * 0.12} rough={0.6} sombra />;
}

/** Uma peça do mundo no lugar dela. */
function PecaNoLugar({ p, m, t, i, icones }) {
  const x = { k: hash(i * 5.3 + p.x), t, m };
  const corpo = p.src ? <IconeEmBlocos cubos={icones[p.src]} alto={1.3} /> : (PECAS[p.peca] ? PECAS[p.peca](x) : null);
  if (!corpo) return null;
  return <group position={[p.x, 0, p.z]} rotation={[0, p.giro || 0, 0]} scale={[p.escala || 1, p.escala || 1, p.escala || 1]}>{corpo}</group>;
}

/* ── A LUZ DE CADA LUGAR ────────────────────────────────────────────────
   Fora: o sol (direcional, com sombra) e o céu (hemisfério). Dentro: a luz
   de estúdio da referência. Debaixo d'água: azul e névoa. */
function Luz({ m }) {
  const f = m.forca || 1;
  if (m.dentro) {
    return (
      <group>
        <hemisphereLight args={[m.recorte, escurecer(m.chao.cor, 0.4), 0.55]} />
        <spotLight position={[2.2, 5.2, 4.4]} angle={0.55} penumbra={0.8} intensity={120} distance={22} decay={2} color={m.luz} castShadow
          shadow-mapSize-width={2048} shadow-mapSize-height={2048} shadow-bias={-0.0003} />
        <spotLight position={[-2.5, 3.6, -2.4]} angle={0.7} penumbra={1} intensity={m.holofotes ? 90 : 40} distance={14} decay={2} color={m.recorte} />
        {m.holofotes && <spotLight position={[2.6, 4.2, -2]} angle={0.5} penumbra={1} intensity={90} distance={14} decay={2} color={m.acento} />}
        <directionalLight position={[-3, 2, 4]} intensity={0.35} color="#fff4e6" />
      </group>
    );
  }
  if (m.subaquatico) {
    // Debaixo d'água: a luz vem de cima, azul, e a cor some com a distância.
    return (
      <group>
        <hemisphereLight args={['#7fd8ff', '#0b3a4f', 1.1]} />
        <directionalLight position={[0, 10, 3]} intensity={1.6} color="#bfefff" castShadow
          shadow-mapSize-width={2048} shadow-mapSize-height={2048} shadow-camera-left={-9} shadow-camera-right={9} shadow-camera-top={9} shadow-camera-bottom={-9} />
        <directionalLight position={[2, 3, 7]} intensity={0.5} color="#9fe6ff" />
      </group>
    );
  }
  const sol = m.solBaixo ? [-8, 3.5, -6] : m.lua ? [6, 9, 2] : [-5, 10, 6];
  return (
    <group>
      <hemisphereLight args={[m.ceu[1], escurecer(m.chao.cor, 0.55), (m.lua ? 0.35 : 0.9) * f]} />
      <directionalLight position={sol} intensity={(m.lua ? 0.7 : 2.6) * f} color={m.luz} castShadow
        shadow-mapSize-width={2048} shadow-mapSize-height={2048} shadow-bias={-0.0004}
        shadow-camera-left={-9} shadow-camera-right={9} shadow-camera-top={9} shadow-camera-bottom={-9} shadow-camera-near={0.5} shadow-camera-far={40} />
      {/* o preenchimento da frente: o rosto não fica no breu contra o sol */}
      <directionalLight position={[2, 3, 7]} intensity={(m.lua ? 0.35 : 0.6) * f} color="#ffffff" />
      <spotLight position={[-2.5, 3.6, -2.4]} angle={0.7} penumbra={1} intensity={m.lua ? 30 : 18} distance={14} decay={2} color={m.recorte} />
    </group>
  );
}


/* ── A COBERTURA À NOITE (2ª referência, 11/10) ───────────────────────────
   Atrás: a janela com a cidade acesa e as luzes grandes que a lente derrete
   em bolas (bokeh). No meio: o parapeito escuro. Na frente: os cartões de
   interface com o ASSUNTO do vídeo, entre a câmera e o elenco — desfocados,
   eles dão a profundidade do quadro da referência. */
function texturaCartaoUso(titulo, linhas, k, uso) {
  const cv = document.createElement('canvas');
  cv.width = 640; cv.height = 400;
  const g = cv.getContext('2d');
  g.fillStyle = 'rgba(18,18,24,0.94)'; g.beginPath(); g.roundRect(4, 4, 632, 392, 26); g.fill();
  g.strokeStyle = 'rgba(255,255,255,0.12)'; g.lineWidth = 2; g.stroke();
  const F = 'Inter, "Helvetica Neue", Arial, sans-serif';
  if (uso != null) {
    g.fillStyle = 'rgba(235,235,245,0.75)'; g.font = `500 30px ${F}`; g.fillText('Uso da sessão', 34, 70);
    g.fillStyle = 'rgba(255,255,255,0.12)'; g.beginPath(); g.roundRect(34, 100, 572, 26, 13); g.fill();
    const gr = g.createLinearGradient(34, 0, 606, 0); gr.addColorStop(0, '#ff7a2b'); gr.addColorStop(1, '#ffc23a');
    g.fillStyle = gr; g.beginPath(); g.roundRect(34, 100, 572 * uso, 26, 13); g.fill();
    g.fillStyle = 'rgba(235,235,245,0.55)'; g.font = `500 24px ${F}`; g.fillText(`${Math.round(uso * 100)}%`, 34, 170);
    return cv;
  }
  g.fillStyle = '#ff6a2b'; g.beginPath(); g.arc(46, 50, 9, 0, Math.PI * 2); g.fill();
  g.fillStyle = '#f2f2f7'; g.font = `600 28px ${F}`; g.fillText(`Claude · sessão ${k + 1}`, 66, 60);
  g.fillStyle = '#ffffff'; g.font = `700 30px ${F}`; g.fillText(String(titulo).slice(0, 30), 34, 134);
  g.fillStyle = 'rgba(225,225,235,0.75)'; g.font = `400 24px ${F}`;
  (linhas || []).slice(0, 3).forEach((l, i) => g.fillText('· ' + String(l).slice(0, 34), 34, 178 + i * 36));
  g.fillStyle = '#ff6a2b'; g.font = `500 22px ${F}`; g.fillText(`● trabalhando · ${30 + k * 7}:${String(10 + k * 13).slice(0, 2)}`, 34, 312);
  g.fillStyle = 'rgba(255,255,255,0.06)'; g.beginPath(); g.roundRect(34, 336, 572, 40, 12); g.fill();
  g.fillStyle = 'rgba(225,225,235,0.45)'; g.font = `400 20px ${F}`; g.fillText('Pergunte ao Claude…', 50, 363);
  return cv;
}
function CartoesDeUso({ paineis = [], t }) {
  const texturas = useMemo(() => {
    const lista = paineis.length ? paineis : [{ titulo: 'Arrumando a tela', linha: '' }];
    const linhasDe = (i) => lista.filter((_, j) => j !== i).slice(0, 3).map((x) => x.titulo);
    const fazer = (cv) => { const tx = new THREE.CanvasTexture(cv); tx.colorSpace = THREE.SRGBColorSpace; tx.anisotropy = 4; return tx; };
    return [fazer(texturaCartaoUso(lista[0].titulo, linhasDe(0), 1)), fazer(texturaCartaoUso(null, null, 0, 0.62 + (lista.length % 4) * 0.08)),
      fazer(texturaCartaoUso((lista[1] || lista[0]).titulo, linhasDe(1), 3))];
  }, [paineis]);
  // Três cartões na frente e embaixo, inclinados para a câmera, flutuando devagar.
  const lugares = [[-1.25, 0.55, 1.35, 0.35], [0.15, 0.32, 1.7, 0.05], [1.35, 0.6, 1.2, -0.35]];
  return (
    <group>
      {lugares.map(([x, y, z, giro], i) => (
        <group key={i} position={[x, y + Math.sin(t * 0.8 + i * 2) * 0.02, z]} rotation={[-0.5, giro, 0]}>
          <mesh>
            <planeGeometry args={[1.28, 0.8]} />
            <meshStandardMaterial map={texturas[i]} emissiveMap={texturas[i]} emissive="#ffffff" emissiveIntensity={0.55} transparent roughness={0.4} toneMapped />
          </mesh>
        </group>
      ))}
    </group>
  );
}
function JanelaDaCidade({ m, t }) {
  const predios = useMemo(() => {
    const out = [], janelas = [];
    for (let i = 0; i < 46; i++) {
      const fila = i % 3, x = -22 + (i / 46) * 44 + (hash(i) - 0.5) * 1.5;
      const z = -9 - fila * 6 - hash(i + 3) * 3, w = 1.6 + hash(i + 7) * 2, h = 3 + hash(i + 11) * (fila === 2 ? 16 : 10);
      out.push({ p: [x, h / 2 - 1.5, z], s: [w, h, 1.5], c: ['#0b1024', '#0e1530', '#0a0e1e'][fila] });
      for (let a = 0; a < h / 0.55; a++) for (let b = 0; b < w / 0.5; b++) {
        if (hash(i * 131 + a * 17 + b * 7) > 0.3) continue;
        janelas.push({ p: [x - w / 2 + 0.25 + b * 0.5, -1.2 + a * 0.55, z + 0.76], s: [0.22, 0.26, 0.02], c: hash(a + b + i) > 0.25 ? '#c9a46a' : '#9fb4d6' });
      }
    }
    return { out, janelas };
  }, []);
  // As luzes grandes: bolinhas que brilham a várias distâncias — a lente faz o bokeh.
  // Poucas e GRANDES, perto do vidro: a lente as derrete nas bolas da referência.
  const luzes = useMemo(() => Array.from({ length: 22 }, (_, i) => ({ p: [(hash(i * 5.1) - 0.5) * 16, 2 + hash(i * 2.3) * 6, -4.6 - hash(i * 7.7) * 5], r: 0.12 + hash(i) * 0.16, c: hash(i * 9) > 0.86 ? '#ff4a5a' : '#fff1d6' })), []);
  return (
    <group>
      <mesh position={[0, 6, -32]}><planeGeometry args={[90, 40]} /><meshBasicMaterial color="#13246e" toneMapped={false} /></mesh>
      <mesh position={[0, 16, -31]}><planeGeometry args={[90, 18]} /><meshBasicMaterial color="#050a24" toneMapped={false} /></mesh>
      <Instancias itens={predios.out} rough={0.9} />
      <Instancias itens={predios.janelas} emissivo={0.32} />
      {luzes.map((l, i) => (
        <mesh key={i} position={l.p}><sphereGeometry args={[l.r, 10, 8]} /><meshBasicMaterial color={l.c} toneMapped={false} /></mesh>
      ))}
      {/* a esquadria do vidro: as colunas finas que dividem a janela */}
      {[-6, -2, 2, 6].map((x) => <B key={x} s={[0.08, 8, 0.08]} p={[x, 3, -3.9]} c="#05060c" r={0.01} sombra={false} />)}
      {/* o parapeito escuro onde o elenco está */}
      <B s={[18, 0.95, 0.5]} p={[0, 0.47, -2.1]} c="#0d0f16" r={0.03} rough={0.5} metal={0.3} />
      <B s={[18, 0.06, 0.56]} p={[0, 0.97, -2.1]} c="#1a1d28" r={0.02} rough={0.35} metal={0.4} />
    </group>
  );
}

/** O mundo inteiro de um trecho: céu, chão, água, paredes, peças e a luz. */
export function Mundo({ m, t, icones = {}, paineis = [] }) {
  const nevoa = m.cobertura ? ['#070c26', 14, 40] : m.subaquatico ? ['#0a4f6e', 2.5, 15] : m.dentro ? [m.parede || '#000', 9, 20] : [m.ceu ? m.ceu[1] : '#000', 16 - (m.neblina || 0) * 6, 46 - (m.neblina || 0) * 14];
  // Fragmento, e não <group>: o "attach" do fundo e da névoa vai para o PAI — dentro de um grupo, eles iam para o grupo e sumiam.
  return (
    <>
      <color attach="background" args={[m.subaquatico ? '#0a4f6e' : m.ceu ? m.ceu[1] : m.parede || '#07061a']} />
      <fog attach="fog" args={nevoa} />
      <Luz m={m} />
      {m.ceu && !m.dentro && !m.subaquatico && <Ceu m={m} t={t} />}
      <Chao m={m} />
      {m.agua && <Mar m={m} t={t} />}
      {m.dentro && !m.telas && !m.cobertura && <Paredes m={m} />}
      {m.cobertura && <JanelaDaCidade m={m} t={t} />}
      {m.cobertura && <CartoesDeUso paineis={paineis} t={t} />}
      {m.props.map((p, i) => <PecaNoLugar key={i} p={p} m={m} t={t} i={i} icones={icones} />)}
      {m.subaquatico && Array.from({ length: 24 }, (_, i) => (
        <B key={'b' + i} s={[0.07, 0.07, 0.07]} p={[(hash(i) - 0.5) * 10, (t * 0.4 + hash(i + 3) * 6) % 6, -1 - hash(i + 7) * 6]} c="#dff6ff" op={0.6} r={0.02} sombra={false} />
      ))}
    </>
  );
}

/* ── OS BICHOS ──────────────────────────────────────────────────────────
   Cada espécie é uma receita de blocos: corpo, pernas, cabeça, focinho
   (a mandíbula de baixo abre com a voz), orelha e rabo (que abana quando o
   bicho fala ou está feliz). forma: 4 (quatro patas), ave, pinguim, peixe, dino. */
const ESP = {
  cachorro: { forma: 4, corpo: [0.4, 0.36, 0.78], perna: 0.3, cab: 0.38, foc: [0.2, 0.16, 0.22], orelha: 'caida', rabo: 'reto', cor: '#b07a45', cor2: '#f1dfc4' },
  gato: { forma: 4, corpo: [0.3, 0.28, 0.62], perna: 0.26, cab: 0.32, foc: [0.14, 0.09, 0.07], orelha: 'pontuda', rabo: 'longo', cor: '#e09a4a', cor2: '#fff1dc' },
  cavalo: { forma: 4, corpo: [0.5, 0.56, 1.3], perna: 0.85, cab: 0.36, foc: [0.26, 0.26, 0.42], orelha: 'pontuda', rabo: 'fofo', cor: '#7a4a2a', cor2: '#3a2414', pescoco: 0.55, crina: true },
  vaca: { forma: 4, corpo: [0.72, 0.66, 1.3], perna: 0.6, cab: 0.46, foc: [0.36, 0.24, 0.16], orelha: 'lado', rabo: 'reto', cor: '#f4f2ee', cor2: '#1d1b1a', manchas: true, chifre: true, focCor: '#f2a9b4' },
  porco: { forma: 4, corpo: [0.56, 0.5, 0.9], perna: 0.24, cab: 0.44, foc: [0.22, 0.17, 0.08], orelha: 'pontuda', rabo: 'enrolado', cor: '#f2a9b4', cor2: '#e98f9d', focCor: '#e98f9d' },
  ovelha: { forma: 4, corpo: [0.62, 0.56, 0.86], perna: 0.3, cab: 0.32, foc: [0.16, 0.14, 0.1], orelha: 'lado', rabo: 'curto', cor: '#f6f4ee', cor2: '#2b2622', cabCor: '#2b2622', fofo: true },
  raposa: { forma: 4, corpo: [0.34, 0.32, 0.72], perna: 0.3, cab: 0.34, foc: [0.16, 0.13, 0.2], orelha: 'pontuda', rabo: 'fofo', cor: '#e2702a', cor2: '#fff4e6' },
  lobo: { forma: 4, corpo: [0.44, 0.42, 0.9], perna: 0.42, cab: 0.4, foc: [0.2, 0.16, 0.24], orelha: 'pontuda', rabo: 'fofo', cor: '#8a8f98', cor2: '#e6e8ec' },
  urso: { forma: 4, corpo: [0.8, 0.74, 1.1], perna: 0.42, cab: 0.56, foc: [0.26, 0.2, 0.16], orelha: 'redonda', rabo: 'curto', cor: '#6b4423', cor2: '#c9a27a' },
  panda: { forma: 4, corpo: [0.8, 0.74, 1.05], perna: 0.42, cab: 0.56, foc: [0.24, 0.18, 0.14], orelha: 'redonda', rabo: 'curto', cor: '#f6f4ee', cor2: '#1d1b1a', panda: true },
  coelho: { forma: 4, corpo: [0.32, 0.32, 0.42], perna: 0.12, cab: 0.32, foc: [0.12, 0.08, 0.06], orelha: 'longa', rabo: 'bola', cor: '#f2ece4', cor2: '#f2b8c4' },
  leao: { forma: 4, corpo: [0.56, 0.52, 1.1], perna: 0.5, cab: 0.44, foc: [0.22, 0.16, 0.16], orelha: 'redonda', rabo: 'longo', cor: '#e0a24a', cor2: '#8a4a1a', juba: true },
  tigre: { forma: 4, corpo: [0.56, 0.52, 1.1], perna: 0.5, cab: 0.44, foc: [0.22, 0.16, 0.16], orelha: 'redonda', rabo: 'longo', cor: '#f08a2a', cor2: '#1d1b1a', listras: true },
  elefante: { forma: 4, corpo: [1.1, 1, 1.5], perna: 0.8, cab: 0.8, foc: [0.22, 0.22, 0.2], orelha: 'grande', rabo: 'curto', cor: '#9aa0aa', cor2: '#c9ced6', tromba: true },
  girafa: { forma: 4, corpo: [0.56, 0.6, 1.05], perna: 1.15, cab: 0.3, foc: [0.18, 0.18, 0.26], orelha: 'pontuda', rabo: 'curto', cor: '#e8b85a', cor2: '#8a5a2c', pescoco: 1.3, manchas: true },
  tartaruga: { forma: 4, corpo: [0.7, 0.36, 0.8], perna: 0.14, cab: 0.26, foc: [0.12, 0.1, 0.1], orelha: 'nenhuma', rabo: 'curto', cor: '#7fae5a', cor2: '#5a7a3a', casco: true },
  galinha: { forma: 'ave', corpo: [0.32, 0.34, 0.4], perna: 0.18, cab: 0.22, cor: '#f6f4ee', bico: '#f1b434', crista: '#e5484d' },
  pato: { forma: 'ave', corpo: [0.34, 0.3, 0.46], perna: 0.12, cab: 0.24, cor: '#f8d24a', bico: '#f97316', bicoLargo: true },
  passaro: { forma: 'ave', corpo: [0.22, 0.22, 0.28], perna: 0.1, cab: 0.18, cor: '#2f80ed', bico: '#f1b434', voa: true },
  papagaio: { forma: 'ave', corpo: [0.26, 0.34, 0.3], perna: 0.12, cab: 0.22, cor: '#2fae5a', bico: '#2b2622', voa: true, rabo2: '#e5484d' },
  coruja: { forma: 'ave', corpo: [0.36, 0.42, 0.34], perna: 0.08, cab: 0.32, cor: '#8a6a4a', bico: '#f1b434', olhao: true },
  pinguim: { forma: 'pinguim', corpo: [0.42, 0.6, 0.36], perna: 0.06, cab: 0.3, cor: '#1d1f26', cor2: '#f6f4ee', bico: '#f97316' },
  peixe: { forma: 'peixe', corpo: [0.18, 0.4, 0.6], cor: '#f97316', cor2: '#ffffff' },
  tubarao: { forma: 'peixe', corpo: [0.4, 0.5, 1.6], cor: '#7f8ea3', cor2: '#eef0f4', barbatana: true },
  golfinho: { forma: 'peixe', corpo: [0.34, 0.4, 1.3], cor: '#7fa6c9', cor2: '#eef4fb', barbatana: true },
  dinossauro: { forma: 'dino', corpo: [0.6, 0.7, 0.9], perna: 0.5, cab: 0.5, cor: '#4fae5a', cor2: '#f1d27a' },
  sapo: { forma: 4, corpo: [0.4, 0.26, 0.42], perna: 0.08, cab: 0.36, foc: [0.3, 0.06, 0.04], orelha: 'nenhuma', rabo: 'nenhum', cor: '#4fae5a', cor2: '#d8f0a0', olhoAlto: true },
  macaco: { forma: 4, corpo: [0.36, 0.42, 0.42], perna: 0.36, cab: 0.36, foc: [0.22, 0.14, 0.08], orelha: 'redonda', rabo: 'longo', cor: '#7a5232', cor2: '#e6c9a0', focCor: '#e6c9a0' },
};

/** A altura da cabeça (para a câmera mirar no rosto do bicho). */
export function alturaDoBicho(especie) {
  const e = ESP[especie] || ESP.cachorro;
  if (e.forma === 'peixe') return 1.2;
  if (e.forma === 'ave') return e.perna + e.corpo[1] + e.cab * 0.6 + (e.voa ? 0.9 : 0);
  if (e.forma === 'pinguim') return e.corpo[1] + e.cab * 0.7;
  if (e.forma === 'dino') return e.perna + e.corpo[1] + e.cab * 0.9;
  return e.perna + e.corpo[1] * 0.9 + (e.pescoco || 0) * 0.9 + e.cab * 0.5;
}

function Rabo({ e, t, abana, cor }) {
  const giro = Math.sin(t * (abana ? 14 : 3)) * (abana ? 0.6 : 0.15);
  if (e.rabo === 'nenhum') return null;
  if (e.rabo === 'bola') return <B s={[0.14, 0.14, 0.14]} p={[0, 0, -0.04]} c="#ffffff" r={0.05} />;
  if (e.rabo === 'enrolado') return <group rotation={[0, giro, 0]}><B s={[0.06, 0.12, 0.06]} p={[0, 0.04, -0.04]} c={cor} r={0.02} /><B s={[0.08, 0.06, 0.06]} p={[0.04, 0.1, -0.06]} c={cor} r={0.02} /></group>;
  const L = e.rabo === 'curto' ? 0.12 : e.rabo === 'longo' ? 0.5 : e.rabo === 'fofo' ? 0.42 : 0.3;
  const W = e.rabo === 'fofo' ? 0.16 : 0.07;
  return (
    <group rotation={[0.7, giro, 0]}>
      <B s={[W, W, L]} p={[0, 0, -L / 2]} c={cor} r={W * 0.4} />
      {e.rabo === 'fofo' && <B s={[W * 0.9, W * 0.9, 0.12]} p={[0, 0, -L - 0.03]} c={e.cor2} r={0.04} />}
    </group>
  );
}

/* A cabeça de quatro patas: o cubo, os olhos de pixel, o focinho com a
   mandíbula que abre na vogal, a orelha de cada espécie. */
function CabecaDeBicho({ e, est, cor }) {
  const s = e.cab;
  const [abre] = est.boca;
  const pisca = clamp(est.pisca);
  const focCor = e.focCor || e.cor2;
  const fz = s / 2 + e.foc[2] / 2;
  return (
    <group>
      <B s={[s, s, s]} c={e.cabCor || cor} r={0.05} rough={0.75} />
      {e.panda && [-1, 1].map((k) => <B key={'p' + k} s={[s * 0.26, s * 0.3, 0.01]} p={[k * s * 0.22, s * 0.08, s / 2 + 0.003]} c="#1d1b1a" r={0.01} sombra={false} />)}
      <Olho p={[-s * 0.24, s * (e.olhoAlto ? 0.45 : 0.12), s / 2 + 0.006]} s={s * 0.15} pisca={pisca} />
      <Olho p={[s * 0.24, s * (e.olhoAlto ? 0.45 : 0.12), s / 2 + 0.006]} s={s * 0.15} pisca={pisca} />
      {/* o focinho: em cima fixo, a mandíbula de baixo abre com a voz */}
      <B s={[e.foc[0], e.foc[1] * 0.6, e.foc[2]]} p={[0, -s * 0.12, fz]} c={focCor} r={0.03} />
      <B s={[e.foc[0] * 0.5, e.foc[1] * 0.3, 0.03]} p={[0, -s * 0.12 + e.foc[1] * 0.2, s / 2 + e.foc[2] + 0.005]} c="#1d1617" r={0.01} sombra={false} />
      <group position={[0, -s * 0.12 - e.foc[1] * 0.3, s / 2]} rotation={[abre * 0.55, 0, 0]}>
        <B s={[e.foc[0] * 0.92, e.foc[1] * 0.4, e.foc[2]]} p={[0, -e.foc[1] * 0.2, e.foc[2] / 2]} c={escurecer(focCor, 0.95)} r={0.03} />
        {abre > 0.25 && <B s={[e.foc[0] * 0.6, 0.02, e.foc[2] * 0.7]} p={[0, 0.0, e.foc[2] / 2]} c="#c2455a" r={0.005} sombra={false} />}
      </group>
      {e.orelha === 'caida' && [-1, 1].map((k) => <B key={k} s={[0.07, s * 0.6, s * 0.36]} p={[k * (s / 2 + 0.035), s * 0.05, -0.02]} rot={[0, 0, k * 0.12]} c={escurecer(cor, 0.75)} r={0.03} />)}
      {e.orelha === 'pontuda' && [-1, 1].map((k) => <B key={k} s={[s * 0.24, s * 0.3, 0.07]} p={[k * s * 0.3, s * 0.62, -0.02]} rot={[0, 0, -k * 0.2]} c={cor} r={0.02} />)}
      {e.orelha === 'redonda' && [-1, 1].map((k) => <B key={k} s={[s * 0.26, s * 0.24, 0.08]} p={[k * s * 0.38, s * 0.56, -0.02]} c={e.panda ? '#1d1b1a' : escurecer(cor, 0.85)} r={0.04} />)}
      {e.orelha === 'longa' && [-1, 1].map((k) => <B key={k} s={[s * 0.2, s * 1.1, 0.07]} p={[k * s * 0.22, s * 0.95, -0.04]} rot={[0, 0, -k * 0.12]} c={cor} r={0.03} />)}
      {e.orelha === 'lado' && [-1, 1].map((k) => <B key={k} s={[s * 0.32, 0.1, s * 0.2]} p={[k * s * 0.62, s * 0.2, -0.02]} c={escurecer(cor, 0.9)} r={0.03} />)}
      {e.orelha === 'grande' && [-1, 1].map((k) => <B key={k} s={[0.08, s * 0.95, s * 0.8]} p={[k * (s / 2 + 0.05), 0, -0.06]} rot={[0, k * 0.3, 0]} c={escurecer(cor, 0.92)} r={0.04} />)}
      {e.chifre && [-1, 1].map((k) => <B key={'c' + k} s={[0.06, 0.16, 0.06]} p={[k * s * 0.34, s * 0.58, 0]} c="#efe6d0" r={0.02} />)}
      {e.juba && <B s={[s * 1.55, s * 1.45, s * 0.5]} p={[0, 0.02, -s * 0.32]} c={e.cor2} r={0.1} rough={1} />}
      {e.tromba && <group position={[0, -s * 0.2, s / 2]} rotation={[0.25 + abre * 0.4, 0, 0]}>{[0, 1, 2, 3].map((i) => <B key={i} s={[0.16 - i * 0.02, 0.18, 0.16 - i * 0.02]} p={[0, -0.12 - i * 0.16, 0.06 + i * 0.03]} c={cor} r={0.04} />)}</group>}
    </group>
  );
}

function Quadrupede({ e, est, cor }) {
  const [W, H, D] = e.corpo;
  const anda = est.acao === 'andar' || est.acao === 'correr' || est.acao === 'pular';
  const passo = est.acao === 'correr' ? 14 : 8;
  const pulo = est.acao === 'pular' || est.acao === 'comemorar' ? Math.abs(Math.sin(est.t * 6)) * 0.25 : 0;
  const respira = Math.sin(est.t * 2.2) * 0.008;
  const y0 = e.perna + H / 2;
  const [cy, cp] = est.cabeca;
  const pescoco = e.pescoco || 0;
  const cabPos = [0, y0 + H * 0.35 + pescoco * 0.85, D / 2 + e.cab * 0.25 + pescoco * 0.25];
  // O corpo vira de lado, e a cabeça volta para a câmera: o bicho se lê inteiro em 3/4.
  return (
    <group position={[0, pulo, 0]}>
      <group rotation={[0, 0.55, 0]}>
        <B s={[W, H, D]} p={[0, y0 + respira, 0]} c={cor} r={0.06} rough={e.fofo ? 1 : 0.75} />
        {e.fofo && [0, 1, 2].map((i) => <B key={i} s={[W * 1.06, H * 0.32, D * 0.3]} p={[0, y0 + H * 0.2, -D * 0.3 + i * D * 0.3]} c={e.cor} r={0.08} rough={1} />)}
        {/* a barriga clara, as manchas, as listras ou o casco */}
        {!e.fofo && !e.casco && <B s={[W * 0.8, H * 0.2, D * 0.8]} p={[0, y0 - H * 0.42, 0]} c={e.cor2} r={0.03} sombra={false} />}
        {e.manchas && [0, 1, 2, 3].map((i) => <B key={'m' + i} s={[W * 1.01, H * 0.32, D * 0.2]} p={[0, y0 + H * (hash(i) - 0.4) * 0.6, -D * 0.35 + i * D * 0.22]} c={e.cor2} r={0.02} sombra={false} />)}
        {e.listras && [0, 1, 2, 3, 4].map((i) => <B key={'l' + i} s={[W * 1.01, H * 0.85, 0.05]} p={[0, y0 + H * 0.05, -D * 0.38 + i * D * 0.19]} c={e.cor2} r={0.01} sombra={false} />)}
        {e.casco && <B s={[W * 1.08, H * 0.9, D * 1.02]} p={[0, y0 + H * 0.2, 0]} c={e.cor2} r={0.14} rough={0.6} />}
        {[[-1, 1], [1, 1], [-1, -1], [1, -1]].map(([kx, kz], i) => {
          const balanca = anda ? Math.sin(est.t * passo + (i === 0 || i === 3 ? 0 : Math.PI)) * 0.5 : 0;
          return (
            <group key={i} position={[kx * (W / 2 - 0.07), e.perna, kz * (D / 2 - 0.1)]} rotation={[balanca, 0, 0]}>
              <B s={[0.12 + W * 0.08, e.perna, 0.12 + W * 0.08]} p={[0, -e.perna / 2, 0]} c={e.panda ? '#1d1b1a' : escurecer(cor, 0.92)} r={0.03} />
            </group>
          );
        })}
        <group position={[0, y0 + H * 0.2, -D / 2]}><Rabo e={e} t={est.t} abana={est.falando || est.emocao === 'feliz' || est.emocao === 'animado'} cor={escurecer(cor, 0.9)} /></group>
        {pescoco > 0 && <B s={[W * 0.4, pescoco + 0.2, W * 0.4]} p={[0, y0 + H * 0.3 + pescoco / 2, D / 2 - 0.05]} rot={[-0.35, 0, 0]} c={cor} r={0.04} />}
        {e.crina && <B s={[0.06, pescoco + 0.15, W * 0.3]} p={[0, y0 + H * 0.45 + pescoco / 2, D / 2 - 0.2]} rot={[-0.35, 0, 0]} c={e.cor2} r={0.02} />}
      </group>
      <group position={[cabPos[0] + Math.sin(0.55) * cabPos[2], cabPos[1], Math.cos(0.55) * cabPos[2]]} rotation={[cp, cy - 0.15, 0]}>
        <CabecaDeBicho e={e} est={est} cor={cor} />
      </group>
    </group>
  );
}

function Ave({ e, est, cor }) {
  const [W, H, D] = e.corpo;
  const [abre] = est.boca;
  const voo = e.voa ? 0.9 + Math.sin(est.t * 2) * 0.08 : 0;
  const bate = e.voa ? Math.sin(est.t * 18) * 0.7 : (est.falando ? Math.sin(est.t * 9) * 0.3 : 0);
  const y0 = e.perna + H / 2 + voo;
  const s = e.cab;
  return (
    <group>
      {!e.voa && [-1, 1].map((k) => <B key={k} s={[0.04, e.perna, 0.04]} p={[k * W * 0.2, e.perna / 2, 0]} c={e.bico} r={0.01} />)}
      <B s={[W, H, D]} p={[0, y0, 0]} c={cor} r={0.06} />
      {[-1, 1].map((k) => <group key={'a' + k} position={[k * W / 2, y0 + H * 0.15, 0]} rotation={[0, 0, k * (0.2 + bate)]}><B s={[0.06, H * 0.7, D * 0.75]} p={[k * 0.03, -H * 0.3, 0]} c={escurecer(cor, 0.85)} r={0.02} /></group>)}
      {e.rabo2 && <B s={[W * 0.4, 0.06, D * 0.7]} p={[0, y0 - H * 0.2, -D * 0.6]} rot={[0.4, 0, 0]} c={e.rabo2} r={0.02} />}
      <group position={[0, y0 + H / 2 + s * 0.4, D * 0.15]} rotation={[est.cabeca[1], est.cabeca[0], 0]}>
        <B s={[s, s, s]} c={cor} r={0.04} />
        <Olho p={[-s * 0.25, s * 0.12, s / 2 + 0.006]} s={s * (e.olhao ? 0.3 : 0.17)} pisca={est.pisca} />
        <Olho p={[s * 0.25, s * 0.12, s / 2 + 0.006]} s={s * (e.olhao ? 0.3 : 0.17)} pisca={est.pisca} />
        <B s={[s * (e.bicoLargo ? 0.6 : 0.3), s * 0.12, s * 0.3]} p={[0, -s * 0.08 + abre * 0.02, s / 2 + s * 0.14]} c={e.bico} r={0.02} />
        <B s={[s * (e.bicoLargo ? 0.55 : 0.26), s * 0.1, s * 0.26]} p={[0, -s * 0.2 - abre * 0.05, s / 2 + s * 0.12]} c={escurecer(e.bico, 0.85)} r={0.02} />
        {e.crista && <B s={[0.05, s * 0.4, s * 0.5]} p={[0, s * 0.6, 0]} c={e.crista} r={0.02} />}
      </group>
    </group>
  );
}

function Pinguim({ e, est }) {
  const [W, H, D] = e.corpo;
  const [abre] = est.boca;
  const ginga = Math.sin(est.t * (est.acao === 'andar' ? 8 : 2)) * (est.acao === 'andar' ? 0.12 : 0.03);
  const s = e.cab;
  return (
    <group rotation={[0, 0, ginga]}>
      {[-1, 1].map((k) => <B key={k} s={[0.14, 0.05, 0.2]} p={[k * 0.1, 0.025, 0.06]} c={e.bico} r={0.02} />)}
      <B s={[W, H, D]} p={[0, H / 2 + 0.04, 0]} c={e.cor} r={0.12} />
      <B s={[W * 0.72, H * 0.8, 0.04]} p={[0, H / 2, D / 2 + 0.005]} c={e.cor2} r={0.06} sombra={false} />
      {[-1, 1].map((k) => <B key={'a' + k} s={[0.06, H * 0.6, D * 0.5]} p={[k * (W / 2 + 0.02), H * 0.55, 0]} rot={[0, 0, k * (0.15 + (est.falando ? Math.abs(Math.sin(est.t * 7)) * 0.4 : 0))]} c={e.cor} r={0.02} />)}
      <group position={[0, H + s * 0.4, 0]} rotation={[est.cabeca[1], est.cabeca[0], 0]}>
        <B s={[s, s, s]} c={e.cor} r={0.06} />
        <B s={[s * 0.7, s * 0.5, 0.03]} p={[0, -0.02, s / 2 + 0.003]} c={e.cor2} r={0.02} sombra={false} />
        <Olho p={[-s * 0.2, s * 0.08, s / 2 + 0.02]} s={s * 0.16} pisca={est.pisca} />
        <Olho p={[s * 0.2, s * 0.08, s / 2 + 0.02]} s={s * 0.16} pisca={est.pisca} />
        <B s={[s * 0.26, s * 0.08, s * 0.24]} p={[0, -s * 0.12 + abre * 0.02, s / 2 + 0.1]} c={e.bico} r={0.02} />
        <B s={[s * 0.22, s * 0.07, s * 0.2]} p={[0, -s * 0.22 - abre * 0.05, s / 2 + 0.09]} c={escurecer(e.bico, 0.85)} r={0.02} />
      </group>
    </group>
  );
}

function Peixe({ e, est }) {
  const [W, H, D] = e.corpo;
  const [abre] = est.boca;
  const nado = Math.sin(est.t * 4) * 0.2;
  return (
    <group position={[0, 1.2 + Math.sin(est.t * 1.5) * 0.06, 0]} rotation={[0, 0.5 + nado * 0.3, 0]}>
      <B s={[W, H, D]} c={e.cor} r={Math.min(W, H) * 0.35} />
      <B s={[W * 0.9, H * 0.35, D * 0.8]} p={[0, -H * 0.3, 0]} c={e.cor2} r={0.04} sombra={false} />
      <group position={[0, 0, -D / 2]} rotation={[0, nado * 1.6, 0]}><B s={[0.05, H * 0.9, D * 0.3]} p={[0, 0, -D * 0.12]} c={e.cor} r={0.02} /></group>
      {e.barbatana && <B s={[0.05, H * 0.6, D * 0.25]} p={[0, H * 0.65, -D * 0.05]} rot={[-0.4, 0, 0]} c={e.cor} r={0.02} />}
      <Olho p={[-W / 2 - 0.004, H * 0.15, D * 0.3]} s={H * 0.18} pisca={est.pisca} />
      <Olho p={[W / 2 + 0.004, H * 0.15, D * 0.3]} s={H * 0.18} pisca={est.pisca} />
      <B s={[W * 0.5, H * 0.12 + abre * H * 0.15, 0.03]} p={[0, -H * 0.1, D / 2 + 0.01]} c="#3a1416" r={0.01} sombra={false} />
    </group>
  );
}

function Dino({ e, est, cor }) {
  const [W, H, D] = e.corpo;
  const [abre] = est.boca;
  const s = e.cab;
  const y0 = e.perna + H / 2;
  return (
    <group rotation={[0, 0.35, 0]}>
      {[-1, 1].map((k) => <B key={k} s={[0.22, e.perna, 0.3]} p={[k * W * 0.3, e.perna / 2, 0]} c={escurecer(cor, 0.9)} r={0.04} />)}
      <B s={[W, H, D]} p={[0, y0, 0]} rot={[-0.35, 0, 0]} c={cor} r={0.08} />
      <B s={[W * 0.7, H * 0.6, 0.04]} p={[0, y0 - 0.05, D / 2 - 0.02]} rot={[-0.35, 0, 0]} c={e.cor2} r={0.02} sombra={false} />
      {[0, 1, 2].map((i) => <B key={'r' + i} s={[W * (0.6 - i * 0.15), H * (0.5 - i * 0.12), 0.4]} p={[0, y0 - 0.2 - i * 0.1, -D / 2 - 0.15 - i * 0.35]} c={cor} r={0.05} />)}
      {[-1, 1].map((k) => <B key={'b' + k} s={[0.08, 0.22, 0.08]} p={[k * W * 0.4, y0 + H * 0.1, D / 2]} rot={[0.8, 0, 0]} c={cor} r={0.02} />)}
      <group position={[0, y0 + H / 2 + s * 0.35, D * 0.4]} rotation={[est.cabeca[1], est.cabeca[0] - 0.2, 0]}>
        <B s={[s, s * 0.7, s * 1.1]} c={cor} r={0.06} />
        <Olho p={[-s * 0.3, s * 0.2, s * 0.4]} s={s * 0.14} pisca={est.pisca} />
        <Olho p={[s * 0.3, s * 0.2, s * 0.4]} s={s * 0.14} pisca={est.pisca} />
        <group position={[0, -s * 0.3, -s * 0.1]} rotation={[abre * 0.5, 0, 0]}>
          <B s={[s * 0.9, s * 0.22, s]} p={[0, -0.04, s * 0.1]} c={escurecer(cor, 0.9)} r={0.04} />
          {[-1, 0, 1].map((k) => <B key={k} s={[0.04, 0.05, 0.04]} p={[k * s * 0.25, 0.06, s * 0.5]} c="#ffffff" r={0.01} sombra={false} />)}
        </group>
      </group>
    </group>
  );
}

/** O bicho da partitura: {especie, cor?, cor2?}; est é o mesmo estado da pessoa (boca, piscada, cabeça, ação). */
export function Animal({ p, est }) {
  const e = { ...(ESP[p.especie] || ESP.cachorro), ...(p.cor2 ? { cor2: p.cor2 } : {}) };
  const cor = p.cor || e.cor;
  if (e.forma === 'ave') return <Ave e={e} est={est} cor={cor} />;
  if (e.forma === 'pinguim') return <Pinguim e={e} est={est} />;
  if (e.forma === 'peixe') return <Peixe e={{ ...e, cor }} est={est} />;
  if (e.forma === 'dino') return <Dino e={e} est={est} cor={cor} />;
  return <Quadrupede e={e} est={est} cor={cor} />;
}

/** A COISA que fala: o ícone em blocos, que estica com a voz e flutua. */
export function Coisa({ p, est, icones = {} }) {
  const [abre] = est.boca;
  const bob = Math.sin(est.t * 1.9 + 0.7) * 0.05;
  const estica = 1 + abre * 0.08, achata = 1 - abre * 0.04;
  const pulo = est.acao === 'pular' || est.acao === 'comemorar' ? Math.abs(Math.sin(est.t * 6)) * 0.25 : 0;
  if (!p.src) return null;
  return (
    <group position={[0, 0.45 + bob + pulo, 0]} rotation={[0, est.cabeca[0] * 0.6, Math.sin(est.t * 1.3) * 0.04]}>
      <group scale={[achata, estica, achata]}>
        <IconeEmBlocos cubos={icones[p.src]} alto={1.25} />
        {/* o rosto de personagem: dois olhos que piscam e a boca que abre com a voz,
            na frente do ícone (o miolo tem 3 camadas de bloco: z = 0,12) */}
        {p.rosto !== false && (
          <group position={[0, 0.66, 0.135]}>
            <Olho p={[-0.16, 0.06, 0]} s={0.11} pisca={est.pisca} />
            <Olho p={[0.16, 0.06, 0]} s={0.11} pisca={est.pisca} />
            <mesh position={[0, -0.14 - abre * 0.03, 0]}><boxGeometry args={[0.14 + abre * 0.04, 0.03 + abre * 0.1, 0.012]} /><meshStandardMaterial color="#3a1416" roughness={0.6} /></mesh>
          </group>
        )}
      </group>
      <B s={[0.7, 0.04, 0.5]} p={[0, -0.43 - bob - pulo, 0]} c="#000000" op={0.18} r={0.02} sombra={false} />
    </group>
  );
}

export const ESPECIES_3D = Object.keys(ESP);
