morfeo 0.7.0 → 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
 
@@ -247,6 +247,8 @@ function validarCapas(capas, donde, err, comps) {
247
247
  }
248
248
  if (c.tipo === 'dividida') for (const lado of ['izq', 'der', 'arriba', 'abajo']) if (c[lado]?.capas) validarCapas(c[lado].capas, `${d}, ${lado}`, err, comps);
249
249
  if (c.tipo === 'linea' && (!Array.isArray(c.hitos) || !c.hitos.length)) err(`${d}: \`linea\` necesita \`hitos\`: [{anio: 1963, texto: '…', en: 'palabra'}]`);
250
+ // `tamano` es una escala del objeto dentro de su recuadro (1 = normal); el tamaño en pantalla es `ancho`
251
+ if ((c.tipo === 'modelo' || c.tipo === 'recurso') && Number(c.tamano) > 10) err(`${d}: \`tamano: ${c.tamano}\` es la escala del objeto (1 = normal, 2 = el doble): con eso la cámara queda dentro. Para el tamaño en pantalla usa \`ancho\` (en px).`);
250
252
  if (c.tipo === 'cintillo' && !c.nombre) err(`${d}: \`cintillo\` necesita \`nombre\``);
251
253
  if (c.tipo === 'llamada' && c.estado != null && !['entrante', 'curso', 'terminada'].includes(c.estado)) err(`${d}: estado "${c.estado}" no existe (entrante, curso o terminada)`);
252
254
  if (c.tipo === 'tierra' && (!esNum(c.lat) || !esNum(c.lon))) err(`${d}: \`tierra\` necesita \`lat\` y \`lon\` (números)`);
@@ -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.0",
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",
@@ -270,7 +270,10 @@ const Post: React.FC<{post: Post3D; estado: Estado}> = ({post, estado}) => {
270
270
  const dist = Math.hypot(estado.pos[0] - estado.mirar[0], estado.pos[1] - estado.mirar[1], estado.pos[2] - estado.mirar[2]);
271
271
  const rango = typeof o.foco === 'number' ? o.foco : Math.max(1.2, dist * 0.55);
272
272
  const efectos: React.ReactElement[] = [];
273
- if (o.foco) efectos.push(<DepthOfField key="f" target={estado.mirar} worldFocusRange={rango} bokehScale={4} resolutionScale={0.5} />);
273
+ // `focusRange` se actualiza en vivo; `worldFocusRange` va en el useMemo de @react-three/postprocessing y, como la
274
+ // distancia cambia en cada cuadro, creaba un DepthOfFieldEffect nuevo por cuadro: en Windows (ANGLE) la GPU se
275
+ // quedaba sin recursos a los ~30 s de video (Shader Error en EffectMaterial y "reading 'alpha'" de null).
276
+ if (o.foco) efectos.push(<DepthOfField key="f" target={estado.mirar} focusRange={rango} bokehScale={4} resolutionScale={0.5} />);
274
277
  if (o.miniatura) efectos.push(<TiltShift2 key="m" blur={0.12 * o.miniatura} taper={0.6} start={[0.5, 0.0]} end={[0.5, 1.0]} />);
275
278
  if (o.brillo) efectos.push(<Bloom key="b" intensity={o.brillo} luminanceThreshold={0.85} luminanceSmoothing={0.2} mipmapBlur />);
276
279
  if (o.vinieta) efectos.push(<Vignette key="v" offset={0.3} darkness={o.vinieta} />);
@@ -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
  }