morfeo 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -31,7 +31,8 @@ export type Objeto3D = 'corazon' | 'estrella' | 'rayo' | 'diamante' | 'trofeo' |
31
31
  | 'valvula' | 'grifo' | 'tinaco' | 'calentador' | 'bomba' | 'alcantarilla' | 'codo' | 'interruptor' | 'enchufe' | 'tablero' | 'medidor' | 'transformador' | 'poste' | 'bateria' | 'turbina' | 'torre_electrica' | 'aire' | 'chip' | 'placa' | 'resistor' | 'capacitor' | 'led' | 'disco_duro' | 'tarjeta_grafica' | 'disipador' | 'ram' | 'router' | 'servidor' | 'fuente_poder' | 'engrane' | 'tornillo' | 'tuerca' | 'motor' | 'resorte' | 'piston'
32
32
  | 'atomo' | 'molecula' | 'adn' | 'celula' | 'virus' | 'cerebro' | 'neurona' | 'pulmon' | 'ojo' | 'diente' | 'hueso' | 'jeringa' | 'matraz' | 'telescopio' | 'iman' | 'sol' | 'tierra' | 'volcan' | 'tornado' | 'iceberg' | 'agujero_negro' | 'disco' | 'usb' | 'tarjeta_sd' | 'cable_hdmi' | 'microfono' | 'visor_vr' | 'cargador' | 'dron' | 'tanque_gas' | 'calentador_solar' | 'termostato' | 'detector_humo' | 'camara_seguridad' | 'antena_celular' | 'presa' | 'torre_enfriamiento' | 'ascensor' | 'fusible' | 'fibra_optica' | 'buzon' | 'autobus' | 'helicoptero' | 'submarino' | 'bicicleta' | 'lapiz' | 'regla' | 'dado' | 'ancla' | 'paraguas'
33
33
  | 'guitarra' | 'piano' | 'tambor' | 'trompeta' | 'zapato' | 'camisa' | 'pulpo' | 'medusa' | 'tortuga' | 'pajaro' | 'buho' | 'moto' | 'pesa' | 'caminadora' | 'raqueta' | 'silbato' | 'golf' | 'tijeras' | 'mapa' | 'sobre' | 'tetera' | 'periodico' | 'pasaporte' | 'ropero' | 'tarjeta' | 'alcancia' | 'grafica' | 'calculadora' | 'pluma' | 'diploma' | 'birrete' | 'estetoscopio' | 'gota' | 'ramen' | 'planta_tratamiento' | 'refineria' | 'pozo_petrolero' | 'subestacion' | 'fosa_septica' | 'tubo_pvc' | 'granero' | 'parada' | 'hospital' | 'galaxia' | 'cometa' | 'lluvia' | 'fosil' | 'wifi' | 'huella' | 'telarana' | 'arbol_navidad'
34
- | 'lago' | 'burbuja' | 'codigo' | 'musculo' | 'doctor' | 'bicicleta_estatica' | 'confeti' | 'registro' | 'cable_submarino';
34
+ | 'lago' | 'burbuja' | 'codigo' | 'musculo' | 'doctor' | 'bicicleta_estatica' | 'confeti' | 'registro' | 'cable_submarino'
35
+ | 'control' | 'consola' | 'cartucho' | 'audifonos' | 'cronometro' | 'termometro' | 'pincel' | 'cartera' | 'caja_fuerte' | 'pastilla' | 'fuegos_artificiales' | 'etiqueta';
35
36
 
36
37
  /** Momento: segundos desde que empieza la escena, una palabra de `say` ("orcas"), "voz", "fin", o "palabra+0.3". */
37
38
  export type Momento = number | string;
package/lib/config.mjs CHANGED
@@ -33,7 +33,7 @@ export const ESCENARIOS = ['bosque', 'cuarto', 'departamento', 'tunel', 'pasillo
33
33
  export const FORMAS_3D = ['moneda', 'billete', 'lingote', 'cubo', 'esfera', 'corazon', 'estrella', 'rayo', 'diamante', 'trofeo', 'cohete', 'foco', 'reloj', 'planeta', 'arbol', 'nube', 'regalo', 'llave', 'candado', 'casa', 'libro', 'globo', 'medalla', 'escudo', 'pin', 'tuberia', 'muro', 'viga', 'columna', 'escalera', 'losa', 'tanque', 'suelo',
34
34
  'valvula', 'grifo', 'tinaco', 'calentador', 'bomba', 'alcantarilla', 'codo', 'interruptor', 'enchufe', 'tablero', 'medidor', 'transformador', 'poste', 'bateria', 'turbina', 'torre_electrica', 'aire', 'chip', 'placa', 'resistor', 'capacitor', 'led', 'disco_duro', 'tarjeta_grafica', 'disipador', 'ram', 'router', 'servidor', 'fuente_poder', 'engrane', 'tornillo', 'tuerca', 'motor', 'resorte', 'piston',
35
35
  'atomo', 'molecula', 'adn', 'celula', 'virus', 'cerebro', 'neurona', 'pulmon', 'ojo', 'diente', 'hueso', 'jeringa', 'matraz', 'telescopio', 'iman', 'sol', 'tierra', 'volcan', 'tornado', 'iceberg', 'agujero_negro', 'disco', 'usb', 'tarjeta_sd', 'cable_hdmi', 'microfono', 'visor_vr', 'cargador', 'dron', 'tanque_gas', 'calentador_solar', 'termostato', 'detector_humo', 'camara_seguridad', 'antena_celular', 'presa', 'torre_enfriamiento', 'ascensor', 'fusible', 'fibra_optica', 'buzon', 'autobus', 'helicoptero', 'submarino', 'bicicleta', 'lapiz', 'regla', 'dado', 'ancla', 'paraguas',
36
- 'guitarra', 'piano', 'tambor', 'trompeta', 'zapato', 'camisa', 'pulpo', 'medusa', 'tortuga', 'pajaro', 'buho', 'moto', 'pesa', 'caminadora', 'raqueta', 'silbato', 'golf', 'tijeras', 'mapa', 'sobre', 'tetera', 'periodico', 'pasaporte', 'ropero', 'tarjeta', 'alcancia', 'grafica', 'calculadora', 'pluma', 'diploma', 'birrete', 'estetoscopio', 'gota', 'ramen', 'planta_tratamiento', 'refineria', 'pozo_petrolero', 'subestacion', 'fosa_septica', 'tubo_pvc', 'granero', 'parada', 'hospital', 'galaxia', 'cometa', 'lluvia', 'fosil', 'wifi', 'huella', 'telarana', 'arbol_navidad', 'lago', 'burbuja', 'codigo', 'musculo', 'doctor', 'bicicleta_estatica', 'confeti', 'registro', 'cable_submarino'];
36
+ 'guitarra', 'piano', 'tambor', 'trompeta', 'zapato', 'camisa', 'pulpo', 'medusa', 'tortuga', 'pajaro', 'buho', 'moto', 'pesa', 'caminadora', 'raqueta', 'silbato', 'golf', 'tijeras', 'mapa', 'sobre', 'tetera', 'periodico', 'pasaporte', 'ropero', 'tarjeta', 'alcancia', 'grafica', 'calculadora', 'pluma', 'diploma', 'birrete', 'estetoscopio', 'gota', 'ramen', 'planta_tratamiento', 'refineria', 'pozo_petrolero', 'subestacion', 'fosa_septica', 'tubo_pvc', 'granero', 'parada', 'hospital', 'galaxia', 'cometa', 'lluvia', 'fosil', 'wifi', 'huella', 'telarana', 'arbol_navidad', 'lago', 'burbuja', 'codigo', 'musculo', 'doctor', 'bicicleta_estatica', 'confeti', 'registro', 'cable_submarino', 'control', 'consola', 'cartucho', 'audifonos', 'cronometro', 'termometro', 'pincel', 'cartera', 'caja_fuerte', 'pastilla', 'fuegos_artificiales', 'etiqueta'];
37
37
  export const PROVEEDORES_VOZ = ['kokoro', 'elevenlabs', 'openai'];
38
38
  export const MUSICAS = ['lofi', 'lofi2', 'chillpop', 'synth', 'house', 'ballad', 'trap'];
39
39
 
@@ -186,13 +186,18 @@ export const PIEZAS = {
186
186
  // lo que faltaba
187
187
  lago: ['lago', 'laguna'], burbuja: ['burbuja'], codigo: ['codigo', 'programacion', 'codigo_fuente'], musculo: ['musculo', 'biceps', 'brazo_fuerte'], doctor: ['doctor', 'doctora', 'medico'],
188
188
  bicicleta_estatica: ['bicicleta_estatica', 'bici_estatica', 'bicicleta_fija'], confeti: ['confeti'], registro: ['registro', 'registro_sanitario', 'caja_de_registro'], cable_submarino: ['cable_submarino'],
189
+ // videojuegos y objetos que la búsqueda resolvía mal (un control salía como una canasta de arcade)
190
+ control: ['control', 'control_de_videojuegos', 'mando', 'gamepad', 'joystick', 'videojuego'], consola: ['consola', 'consola_de_videojuegos'],
191
+ cartucho: ['cartucho', 'cartucho_de_videojuego'], audifonos: ['audifonos', 'auriculares', 'diadema'], cronometro: ['cronometro'],
192
+ termometro: ['termometro', 'temperatura', 'fiebre'], pincel: ['pincel', 'brocha'], cartera: ['cartera', 'billetera'], caja_fuerte: ['caja_fuerte'],
193
+ pastilla: ['pastilla', 'capsula', 'medicina', 'medicamento'], fuegos_artificiales: ['fuegos_artificiales', 'pirotecnia'], etiqueta: ['etiqueta', 'etiqueta_de_precio'],
189
194
  };
190
195
  const PIEZA_DE = new Map();
191
196
  for (const [forma, terminos] of Object.entries(PIEZAS)) for (const t of terminos) PIEZA_DE.set(t, forma);
192
197
 
193
198
  /** Formas básicas de Morfeo (objetos3d.tsx) por término: se usan solo si ninguna biblioteca tiene el objeto. */
194
199
  const BASICAS = {corazon: ['corazon'], estrella: ['estrella'], rayo: ['rayo', 'electricidad'], diamante: ['diamante', 'gema'], trofeo: ['trofeo', 'copa'], cohete: ['cohete'], foco: ['foco', 'bombilla', 'foco_led'], reloj: ['reloj'],
195
- planeta: ['planeta', 'marte', 'saturno'], arbol: ['arbol'], nube: ['nube'], regalo: ['regalo'], llave: ['llave'], candado: ['candado'], casa: ['casa'], libro: ['libro'], globo: ['globo'], medalla: ['medalla'], escudo: ['escudo'], tanque: ['cisterna']};
200
+ planeta: ['planeta', 'marte', 'saturno'], arbol: ['arbol'], nube: ['nube'], regalo: ['regalo'], llave: ['llave'], candado: ['candado'], casa: ['casa'], libro: ['libro'], globo: ['globo'], medalla: ['medalla'], escudo: ['escudo'], billete: ['billete', 'billetes', 'dinero'], tanque: ['cisterna']};
196
201
  const BASICA_DE = new Map();
197
202
  for (const [forma, terminos] of Object.entries(BASICAS)) for (const t of terminos) BASICA_DE.set(t, forma);
198
203
  export function basicaDe(termino) {
package/lib/recursos.mjs CHANGED
@@ -101,7 +101,8 @@ function puntaje(nombrePalabras, consulta) {
101
101
  let p = 0;
102
102
  for (const w of q) {
103
103
  if (n.includes(w)) p += 10;
104
- else if (w.length >= 4 && n.some((x) => x.length >= 4 && (x.startsWith(w) || w.startsWith(x)))) p += 4;
104
+ // variantes de la misma palabra ("pipe" / "pipes", "tree" / "trees"), no compuestas: "gamepad" no es "game"
105
+ else if (w.length >= 4 && n.some((x) => x.length >= 4 && Math.abs(x.length - w.length) <= 1 && (x.startsWith(w) || w.startsWith(x)))) p += 4;
105
106
  else return 0;
106
107
  }
107
108
  if (n[n.length - 1] === q[q.length - 1]) p += 6; // "modular pipe" > "pipe wrench" al buscar "pipe"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morfeo",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Genera videos verticales animados (Remotion + Three.js) con voz, música y subtítulos, desde un guion en código o escrito por IA.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -7,6 +7,8 @@ import * as THREE from 'three';
7
7
  import {ACERO, Aro, C, E, LATON, ORO, Tubo, Y, type M, type V3} from './objetos-ingenieria';
8
8
  import {Cono, Ov, azar} from './objetos-ciencia';
9
9
 
10
+ const easeOutCubic = (x: number) => 1 - (1 - Math.min(1, Math.max(0, x))) ** 3;
11
+
10
12
  export const OBJETOS_VARIOS = [
11
13
  // música
12
14
  'guitarra', 'piano', 'tambor', 'trompeta',
@@ -24,6 +26,8 @@ export const OBJETOS_VARIOS = [
24
26
  'galaxia', 'cometa', 'lluvia', 'fosil', 'wifi', 'huella', 'telarana', 'arbol_navidad',
25
27
  // lo que faltaba
26
28
  'lago', 'burbuja', 'codigo', 'musculo', 'doctor', 'bicicleta_estatica', 'confeti', 'registro', 'cable_submarino',
29
+ // videojuegos y objetos que la búsqueda resolvía mal
30
+ 'control', 'consola', 'cartucho', 'audifonos', 'cronometro', 'termometro', 'pincel', 'cartera', 'caja_fuerte', 'pastilla', 'fuegos_artificiales', 'etiqueta',
27
31
  ] as const;
28
32
 
29
33
  const MADERA = {c: '#B8793F', r: 0.6};
@@ -1010,6 +1014,228 @@ export const ObjetoVario: React.FC<{forma: string; t: number; opciones?: Record<
1010
1014
  </group>
1011
1015
  );
1012
1016
  }
1017
+ // ------------------------------------------------------------ videojuegos y objetos que faltaban
1018
+ case 'control': {
1019
+ const cuerpo = k('#2B2E36');
1020
+ const pulsa = (i: number) => (Math.sin(t * 3 - i * 1.4) > 0.85 ? 1 : 0);
1021
+ return (
1022
+ // los botones miran a la cámara: el control se inclina hacia adelante, no se ve de canto
1023
+ <group rotation={[1.15, 0, 0]} position={[0, 0.05, 0]} scale={1.15}>
1024
+ <C s={[1.5, 0.26, 0.72]} c={cuerpo} r={0.5} />
1025
+ {[-1, 1].map((l) => (
1026
+ <group key={l}>
1027
+ <Ov p={[l * 0.72, -0.02, -0.08]} s={[0.36, 0.17, 0.42]} c={cuerpo} r={0.5} />
1028
+ <Ov p={[l * 0.86, -0.04, 0.38]} s={[0.24, 0.15, 0.4]} rot={[0, l * 0.35, 0]} c={cuerpo} r={0.5} />
1029
+ <C p={[l * 0.62, 0.02, -0.42]} s={[0.36, 0.14, 0.12]} c="#1B1C22" r={0.5} />
1030
+ </group>
1031
+ ))}
1032
+ {/* cruceta */}
1033
+ <C p={[-0.55, 0.15, -0.06]} s={[0.26, 0.06, 0.08]} c="#1B1C22" r={0.5} />
1034
+ <C p={[-0.55, 0.15, -0.06]} s={[0.08, 0.06, 0.26]} c="#1B1C22" r={0.5} />
1035
+ {/* botones */}
1036
+ {([[0.55, -0.18, '#3D7BFF'], [0.55, 0.06, '#2EC27E'], [0.43, -0.06, '#FFD23F'], [0.67, -0.06, '#E5484D']] as [number, number, string][]).map(([x, z, c], i) => (
1037
+ <Y key={i} p={[x, 0.15 - pulsa(i) * 0.025, z]} rad={0.055} h={0.07} c={c} e={0.2 + pulsa(i) * 0.6} r={0.35} />
1038
+ ))}
1039
+ {/* palancas */}
1040
+ {([[-0.28, 0.2], [0.28, 0.2]] as [number, number][]).map(([x, z], i) => (
1041
+ <group key={i} rotation={[Math.sin(t * 1.3 + i) * 0.18, 0, Math.cos(t * 1.1 + i) * 0.18]} position={[x, 0.13, z]}>
1042
+ <Y rad={0.12} h={0.06} c="#1B1C22" r={0.6} />
1043
+ <Y p={[0, 0.07, 0]} rad={0.04} h={0.12} c="#3A3D45" r={0.5} />
1044
+ <Y p={[0, 0.14, 0]} rad={[0.1, 0.11]} h={0.05} c="#3A3D45" r={0.7} />
1045
+ </group>
1046
+ ))}
1047
+ <Y p={[0, 0.14, -0.2]} rad={0.05} h={0.03} c="#F7F5F0" e={0.6} />
1048
+ </group>
1049
+ );
1050
+ }
1051
+ case 'consola':
1052
+ return (
1053
+ <group rotation={[0.3, -0.45, 0]} position={[0, -0.1, 0]}>
1054
+ <C s={[1.9, 0.42, 1.25]} c={k('#E9E7E2')} r={0.45} />
1055
+ <C p={[0, 0.215, 0]} s={[1.92, 0.02, 1.27]} c="#1B1C22" r={0.5} />
1056
+ <C p={[0.1, 0.32, -0.05]} s={[1.6, 0.2, 1.05]} c={k('#E9E7E2')} r={0.45} />
1057
+ <C p={[0, 0.06, 0.63]} s={[1.0, 0.04, 0.02]} c="#1B1C22" />
1058
+ <C p={[-0.7, 0.06, 0.63]} s={[0.12, 0.06, 0.02]} c="#3D7BFF" e={0.5 + 0.4 * Math.sin(t * 2)} />
1059
+ {[-0.25, 0.05].map((x) => (
1060
+ <C key={x} p={[x + 0.5, -0.06, 0.63]} s={[0.18, 0.06, 0.02]} c="#1B1C22" />
1061
+ ))}
1062
+ {Array.from({length: 7}, (_, i) => (
1063
+ <C key={i} p={[-0.95, 0.12 - i * 0.05, -0.3 + (i % 2) * 0.02]} s={[0.02, 0.025, 0.6]} c="#9A9DA4" />
1064
+ ))}
1065
+ </group>
1066
+ );
1067
+ case 'cartucho':
1068
+ return (
1069
+ <group rotation={[-0.15, -0.3, 0.08]}>
1070
+ <C s={[1.3, 1.45, 0.24]} c={k('#7C7F87')} r={0.55} />
1071
+ <C p={[0, 0.64, 0]} s={[1.18, 0.18, 0.26]} c={k('#7C7F87')} r={0.55} />
1072
+ {[-0.3, -0.18, -0.06, 0.06, 0.18, 0.3].map((x) => (
1073
+ <C key={x} p={[x, 0.64, 0.135]} s={[0.07, 0.12, 0.01]} c="#5E6168" />
1074
+ ))}
1075
+ <C p={[0, 0.02, 0.125]} s={[1.0, 0.9, 0.01]} c="#F7F5F0" r={0.6} />
1076
+ <C p={[0, 0.2, 0.135]} s={[0.86, 0.4, 0.01]} c="#E5484D" e={0.15} r={0.5} />
1077
+ <C p={[0, -0.18, 0.135]} s={[0.6, 0.08, 0.01]} c="#1B1C22" />
1078
+ <C p={[0, -0.32, 0.135]} s={[0.4, 0.05, 0.01]} c="#9A9DA4" />
1079
+ <C p={[0, -0.75, 0]} s={[0.9, 0.06, 0.16]} c="#3A3D45" />
1080
+ {Array.from({length: 10}, (_, i) => (
1081
+ <C key={i} p={[-0.36 + i * 0.08, -0.75, 0.085]} s={[0.04, 0.05, 0.01]} {...ORO} />
1082
+ ))}
1083
+ <C p={[0.5, 0.45, 0.13]} s={[0.12, 0.12, 0.01]} c="#5E6168" />
1084
+ </group>
1085
+ );
1086
+ case 'audifonos':
1087
+ return (
1088
+ <group rotation={[0.1, -0.35, 0]}>
1089
+ <Aro p={[0, 0.15, 0]} rad={0.78} tubo={0.075} arco={Math.PI} c={k('#2B2E36')} r={0.45} />
1090
+ <Aro p={[0, 0.15, 0]} rad={0.73} tubo={0.05} arco={Math.PI * 0.7} rot={[0, 0, Math.PI * 0.15]} c="#E5484D" r={0.6} />
1091
+ {[-1, 1].map((l) => (
1092
+ <group key={l} position={[l * 0.78, -0.12, 0]}>
1093
+ <C p={[0, 0.2, 0]} s={[0.08, 0.22, 0.1]} {...ACERO} />
1094
+ <Y eje="x" rad={0.34} h={0.22} c={k('#2B2E36')} r={0.45} />
1095
+ <Y eje="x" p={[-l * 0.13, 0, 0]} rad={0.3} h={0.08} c="#3A3D45" r={0.9} />
1096
+ <Y eje="x" p={[l * 0.115, 0, 0]} rad={0.2} h={0.02} c="#E5484D" r={0.5} e={0.15} />
1097
+ </group>
1098
+ ))}
1099
+ </group>
1100
+ );
1101
+ case 'cronometro': {
1102
+ const vuelta = (t * 0.5) % 1;
1103
+ return (
1104
+ <group rotation={[0.08, -0.2, 0]}>
1105
+ <Y eje="z" rad={0.85} h={0.26} c={k('#C9CED6')} m={0.8} r={0.3} seg={40} />
1106
+ <Y eje="z" p={[0, 0, 0.12]} rad={0.75} h={0.04} c="#F7F5F0" r={0.5} seg={40} />
1107
+ {Array.from({length: 12}, (_, i) => {
1108
+ const a = (i / 12) * Math.PI * 2;
1109
+ return <C key={i} p={[Math.sin(a) * 0.62, Math.cos(a) * 0.62, 0.15]} rot={[0, 0, -a]} s={[0.03, i % 3 === 0 ? 0.13 : 0.07, 0.01]} c="#1B1C22" />;
1110
+ })}
1111
+ <group position={[0, 0, 0.16]} rotation={[0, 0, -vuelta * Math.PI * 2]}>
1112
+ <C p={[0, 0.27, 0]} s={[0.035, 0.56, 0.01]} c="#E5484D" e={0.3} />
1113
+ </group>
1114
+ <Y eje="z" p={[0, 0, 0.17]} rad={0.05} h={0.02} c="#1B1C22" />
1115
+ <Y p={[0, 0.98, 0]} rad={0.1} h={0.18} c={k('#C9CED6')} m={0.8} r={0.3} />
1116
+ <Y p={[0, 1.1, 0]} rad={0.17} h={0.08} c="#E5484D" r={0.4} />
1117
+ <Aro p={[0, 1.3, 0]} rad={0.14} tubo={0.04} c={k('#C9CED6')} m={0.8} r={0.3} />
1118
+ <Y p={[0.62, 0.62, 0]} rot={[0, 0, -Math.PI / 4]} rad={0.07} h={0.16} c={k('#C9CED6')} m={0.8} r={0.3} />
1119
+ </group>
1120
+ );
1121
+ }
1122
+ case 'termometro': {
1123
+ const nivel = 0.45 + 0.35 * (0.5 + 0.5 * Math.sin(t * 1.2));
1124
+ return (
1125
+ <group rotation={[0, 0, -0.18]}>
1126
+ <Y p={[0, 0.25, 0]} rad={0.15} h={1.9} c="#DDEAF5" o={0.45} r={0.1} />
1127
+ <E p={[0, -0.82, 0]} rad={0.28} c={k('#E5484D')} r={0.3} e={0.2} />
1128
+ <Y p={[0, -0.62 + nivel * 0.8, 0]} rad={0.07} h={0.4 + nivel * 1.6} c={k('#E5484D')} e={0.25} r={0.3} />
1129
+ {Array.from({length: 8}, (_, i) => (
1130
+ <C key={i} p={[0.17, -0.35 + i * 0.18, 0]} s={[i % 2 ? 0.05 : 0.1, 0.018, 0.02]} c="#1B1C22" />
1131
+ ))}
1132
+ <Y p={[0, 1.22, 0]} rad={0.15} h={0.05} c="#C9CED6" m={0.8} />
1133
+ </group>
1134
+ );
1135
+ }
1136
+ case 'pincel':
1137
+ return (
1138
+ <group rotation={[0, 0, -0.7]}>
1139
+ <Y p={[0, 0.55, 0]} rad={[0.07, 0.1]} h={1.3} c={k('#C0392B')} r={0.35} />
1140
+ <Y p={[0, -0.22, 0]} rad={[0.1, 0.11]} h={0.3} c="#C9CED6" m={0.85} r={0.3} />
1141
+ <Y p={[0, -0.52, 0]} rad={[0.11, 0.03]} h={0.36} c="#3B2A1E" r={0.9} />
1142
+ <Y p={[0, -0.69, 0]} rad={[0.035, 0.005]} h={0.08} c="#3D7BFF" r={0.2} e={0.2} />
1143
+ <E p={[0.2, -0.95, 0]} rad={0.05} c="#3D7BFF" r={0.2} />
1144
+ </group>
1145
+ );
1146
+ case 'cartera':
1147
+ return (
1148
+ <group rotation={[0.3, -0.4, 0]}>
1149
+ <C s={[1.5, 1.0, 0.22]} c={k('#7A4A2A')} r={0.75} />
1150
+ <C p={[0, 0.3, 0.12]} s={[1.5, 0.42, 0.04]} c={k('#7A4A2A')} r={0.75} />
1151
+ <C p={[0, 0.2, 0.15]} s={[1.44, 0.02, 0.01]} c="#4E2E1A" />
1152
+ <C p={[0.42, 0.62, 0.02]} s={[0.55, 0.3, 0.12]} c="#6BBF59" r={0.8} />
1153
+ <C p={[-0.25, 0.58, 0.06]} s={[0.6, 0.22, 0.04]} c="#3D7BFF" r={0.4} />
1154
+ <Y eje="z" p={[0.55, 0.3, 0.15]} rad={0.07} h={0.03} {...LATON} />
1155
+ </group>
1156
+ );
1157
+ case 'caja_fuerte': {
1158
+ const gira = Math.sin(t * 1.4) * 1.2;
1159
+ return (
1160
+ <group rotation={[0.1, -0.45, 0]}>
1161
+ <C s={[1.5, 1.6, 1.3]} c={k('#4A4F59')} m={0.6} r={0.4} />
1162
+ <C p={[0, 0, 0.66]} s={[1.24, 1.34, 0.04]} c={k('#5B616C')} m={0.6} r={0.4} />
1163
+ <group position={[0.05, 0.12, 0.7]} rotation={[0, 0, gira]}>
1164
+ <Y eje="z" rad={0.28} h={0.08} c="#C9CED6" m={0.85} r={0.25} seg={32} />
1165
+ {Array.from({length: 10}, (_, i) => (
1166
+ <C key={i} p={[Math.sin((i / 10) * Math.PI * 2) * 0.22, Math.cos((i / 10) * Math.PI * 2) * 0.22, 0.045]} rot={[0, 0, -(i / 10) * Math.PI * 2]} s={[0.02, 0.06, 0.01]} c="#1B1C22" />
1167
+ ))}
1168
+ <Y eje="z" p={[0, 0, 0.06]} rad={0.08} h={0.06} c="#3A3D45" />
1169
+ </group>
1170
+ <C p={[0.05, -0.38, 0.72]} s={[0.5, 0.08, 0.08]} c="#C9CED6" m={0.85} r={0.25} />
1171
+ {[-0.45, 0.45].map((y) => (
1172
+ <C key={y} p={[-0.66, y, 0.6]} s={[0.08, 0.22, 0.12]} c="#2B2E36" m={0.6} r={0.4} />
1173
+ ))}
1174
+ </group>
1175
+ );
1176
+ }
1177
+ case 'pastilla':
1178
+ return (
1179
+ <group rotation={[0.3, 0.2, -0.6 + Math.sin(t * 1.2) * 0.05]}>
1180
+ <Y p={[0, 0.32, 0]} rad={0.36} h={0.64} c={k('#E5484D')} r={0.25} seg={32} />
1181
+ <E p={[0, 0.64, 0]} rad={0.36} c={k('#E5484D')} r={0.25} />
1182
+ <Y p={[0, -0.32, 0]} rad={0.36} h={0.64} c="#F7F5F0" r={0.25} seg={32} />
1183
+ <E p={[0, -0.64, 0]} rad={0.36} c="#F7F5F0" r={0.25} />
1184
+ <Ov p={[-0.17, 0.45, 0.24]} s={[0.06, 0.22, 0.03]} c="#FFFFFF" o={0.7} e={0.5} />
1185
+ </group>
1186
+ );
1187
+ case 'fuegos_artificiales': {
1188
+ const colores = ['#FFD23F', '#E5484D', '#3D7BFF', '#2EC27E', '#C061CB'];
1189
+ return (
1190
+ <group>
1191
+ {[0, 1, 2].map((n) => {
1192
+ const f = (t * 0.4 + n * 0.34) % 1;
1193
+ const sube = Math.min(1, f / 0.3);
1194
+ const abre = Math.max(0, (f - 0.3) / 0.7);
1195
+ const centro: V3 = [-0.75 + n * 0.75, -1.0 + easeOutCubic(sube) * (1.55 + (n % 2) * 0.35), -n * 0.15];
1196
+ const c = colores[(n * 2 + 1) % colores.length];
1197
+ const r = easeOutCubic(abre) * (0.7 + (n % 2) * 0.15);
1198
+ const luz = Math.max(0, 1 - abre * 1.1);
1199
+ return (
1200
+ <group key={n}>
1201
+ {f < 0.3 ? (
1202
+ <group>
1203
+ <E p={centro} rad={0.06} c="#FFF2B0" e={1.2} />
1204
+ <C p={[centro[0], centro[1] - 0.18, centro[2]]} s={[0.03, 0.3, 0.03]} c="#FFB347" e={0.8} o={0.7} />
1205
+ </group>
1206
+ ) : null}
1207
+ {abre > 0 ? (
1208
+ <group position={[centro[0], centro[1] - abre * abre * 0.3, centro[2]]}>
1209
+ {Array.from({length: 14}, (_, i) => {
1210
+ const a = (i / 14) * Math.PI * 2;
1211
+ return (
1212
+ <group key={i} rotation={[0, 0, a]}>
1213
+ <C p={[r * 0.82, 0, 0]} s={[Math.max(0.02, r * 0.38), 0.035, 0.035]} c={c} e={1.1 * luz} o={luz} />
1214
+ <E p={[r, 0, 0]} rad={0.07 * (0.4 + luz * 0.6)} c={i % 2 ? c : '#FFF2B0'} e={1.3 * luz} />
1215
+ </group>
1216
+ );
1217
+ })}
1218
+ <E rad={0.12 * luz} c="#FFFFFF" e={1.5} o={luz} />
1219
+ </group>
1220
+ ) : null}
1221
+ </group>
1222
+ );
1223
+ })}
1224
+ </group>
1225
+ );
1226
+ }
1227
+ case 'etiqueta':
1228
+ return (
1229
+ <group rotation={[0, -0.3, 0.25 + Math.sin(t * 1.6) * 0.06]}>
1230
+ <mesh>
1231
+ <extrudeGeometry args={[(() => { const s = new THREE.Shape(); s.moveTo(-0.55, -0.75); s.lineTo(0.55, -0.75); s.lineTo(0.55, 0.45); s.lineTo(0, 0.9); s.lineTo(-0.55, 0.45); s.closePath(); const h = new THREE.Path(); h.absarc(0, 0.5, 0.11, 0, Math.PI * 2, true); s.holes.push(h); return s; })(), {depth: 0.06, bevelEnabled: false}]} />
1232
+ <meshStandardMaterial color={k('#FFD23F')} roughness={0.6} />
1233
+ </mesh>
1234
+ <C p={[0, -0.05, 0.065]} s={[0.7, 0.16, 0.01]} c="#1B1C22" />
1235
+ <C p={[0, -0.35, 0.065]} s={[0.45, 0.08, 0.01]} c="#1B1C22" />
1236
+ <Tubo pts={[[0, 0.5, 0.03], [0.15, 0.85, 0.05], [0.05, 1.2, 0.02]]} rad={0.02} c="#E5484D" r={0.6} />
1237
+ </group>
1238
+ );
1013
1239
  default:
1014
1240
  return null;
1015
1241
  }