primocode 9.10.0 → 10.0.9

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.
Files changed (89) hide show
  1. package/README.md +30 -6
  2. package/bin/primocode.js +169 -342
  3. package/lib/act.js +360 -39
  4. package/lib/browser.js +105 -4
  5. package/lib/casar.js +108 -0
  6. package/lib/catalogo.js +6 -2
  7. package/lib/claude-engine.js +38 -17
  8. package/lib/codex-engine.js +27 -9
  9. package/lib/config.js +10 -3
  10. package/lib/cursor.js +10 -1
  11. package/lib/entrada.js +77 -11
  12. package/lib/estudio/cinema.js +27 -9
  13. package/lib/estudio/diretor.js +21 -1
  14. package/lib/estudio/ende.js +16 -5
  15. package/lib/estudio/estilo.js +66 -0
  16. package/lib/estudio/gerativo.js +1453 -0
  17. package/lib/estudio/glifos.js +215 -0
  18. package/lib/estudio/hibrido.js +165 -0
  19. package/lib/estudio/index.js +284 -53
  20. package/lib/estudio/kit/base.jsx +285 -0
  21. package/lib/estudio/kit/gerativo.jsx +980 -0
  22. package/lib/estudio/kit/index.jsx +13 -0
  23. package/lib/estudio/kit/marca.jsx +741 -0
  24. package/lib/estudio/kit/tres.jsx +308 -0
  25. package/lib/estudio/kit/vox.jsx +357 -0
  26. package/lib/estudio/kit.js +78 -0
  27. package/lib/estudio/lancamento.js +441 -0
  28. package/lib/estudio/motion.js +2 -5
  29. package/lib/estudio/plano.js +28 -3
  30. package/lib/estudio/referencias.js +105 -0
  31. package/lib/estudio/remotion.js +139 -51
  32. package/lib/estudio/ritmo.js +77 -0
  33. package/lib/estudio/sons-leves.js +38 -1
  34. package/lib/estudio/variedade.js +105 -0
  35. package/lib/estudio/vox.js +414 -0
  36. package/lib/fala.js +1 -94
  37. package/lib/mcp.js +156 -0
  38. package/lib/personas.js +67 -0
  39. package/lib/pipeline.js +55 -56
  40. package/lib/repeticao.js +3 -1
  41. package/lib/skills.js +44 -2
  42. package/lib/snapshot.js +111 -0
  43. package/lib/tools.js +100 -22
  44. package/lib/ui.js +50 -8
  45. package/lib/video/analise.js +89 -0
  46. package/lib/video/auto.js +329 -0
  47. package/lib/video/camadas.js +207 -0
  48. package/lib/video/cli.js +74 -0
  49. package/lib/video/delta.js +90 -0
  50. package/lib/video/edits.js +379 -0
  51. package/lib/video/filters.js +280 -0
  52. package/lib/video/fx3d/Camadas.jsx +64 -0
  53. package/lib/video/fx3d/Objetos.jsx +128 -0
  54. package/lib/video/fx3d/Root.jsx +18 -0
  55. package/lib/video/fx3d/modelos.jsx +304 -0
  56. package/lib/video/fx3d.js +108 -0
  57. package/lib/video/gerados.js +119 -0
  58. package/lib/video/objetos.js +111 -0
  59. package/lib/video/ops-fx.js +453 -0
  60. package/lib/video/ops.js +347 -0
  61. package/lib/video/paralelo.js +50 -0
  62. package/lib/video/pedido.js +191 -0
  63. package/lib/video/projeto.js +133 -0
  64. package/lib/video/rastrear.js +95 -0
  65. package/lib/video/rastro.js +276 -0
  66. package/lib/video/render.js +274 -0
  67. package/lib/video/texto.js +144 -0
  68. package/lib/video/timeline.js +318 -0
  69. package/lib/video/transcript.js +124 -0
  70. package/lib/voz.js +75 -223
  71. package/package.json +2 -3
  72. package/skills/motion-de-primeira/SKILL.md +5 -5
  73. package/studio/narrar.py +10 -7
  74. package/studio/rastrear.py +399 -0
  75. package/studio/texto.py +208 -0
  76. package/voz/primo_voz/__main__.py +96 -236
  77. package/voz/primo_voz/transcrever.py +31 -1
  78. package/app/ansi.js +0 -55
  79. package/app/app.css +0 -500
  80. package/app/app.js +0 -1017
  81. package/app/icone.svg +0 -6
  82. package/app/index.html +0 -220
  83. package/app/primo-marca.js +0 -192
  84. package/lib/app.js +0 -615
  85. package/lib/icone.js +0 -263
  86. package/lib/instalar-app.js +0 -231
  87. package/lib/painel.js +0 -291
  88. package/voz/primo_voz/desperta.py +0 -195
  89. package/voz/primo_voz/orbe.py +0 -406
@@ -0,0 +1,308 @@
1
+ /* kit/tres.jsx — o PRODUTO EM 3D, feito de geometria, sem arquivo de fora.
2
+ *
3
+ * "Crie um vídeo para anunciar um headset — o vídeo deve ter um 3D do fone"
4
+ * (30/09), com "sem referências": nada de foto, então o produto é modelado
5
+ * aqui, com as peças do three.js, e iluminado como em estúdio.
6
+ *
7
+ * {"tipo":"kit","peca":"produto3d","modelo":"fone"|"celular"|"lata"|"caixa",
8
+ * "cor":"#1b1c20","corAcento":"#ff5a1f","t":0,"giro":0.35,
9
+ * "x":0,"y":0,"largura":1920,"altura":1080}
10
+ *
11
+ * O movimento: entra pequeno e girado e assenta com mola (0,9 s), depois gira
12
+ * devagar e flutua — produto de vitrine, nunca pião. A luz de recorte vem na
13
+ * cor de acento: é ela que desenha a silhueta no fundo escuro. */
14
+ import React, { useMemo } from 'react';
15
+ import { ThreeCanvas } from '@remotion/three';
16
+ import * as THREE from 'three';
17
+ import { useThree } from '@react-three/fiber';
18
+ import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
19
+
20
+ /* A LUZ DE ESTÚDIO: sem um ambiente, material brilhante ou metálico não tem o
21
+ que refletir e sai preto ("a engrenagem parecia uma mancha", 30/09). A sala
22
+ do three (RoomEnvironment, local, sem rede) vira o reflexo de tudo. */
23
+ function Ambiente({ forca = 1 }) {
24
+ const { gl, scene } = useThree();
25
+ useMemo(() => {
26
+ const pm = new THREE.PMREMGenerator(gl);
27
+ scene.environment = pm.fromScene(new RoomEnvironment(), 0.04).texture;
28
+ scene.environmentIntensity = forca;
29
+ pm.dispose();
30
+ }, [gl, scene]);
31
+ return null;
32
+ }
33
+
34
+ const saida = (x) => 1 - Math.pow(1 - Math.max(0, Math.min(1, x)), 3);
35
+
36
+ function Material({ cor, brilho = 0.35, metal = 0.25, verniz = 0.8, emissivo }) {
37
+ return <meshPhysicalMaterial color={cor} roughness={brilho} metalness={metal} clearcoat={verniz} clearcoatRoughness={0.25}
38
+ {...(emissivo ? { emissive: emissivo, emissiveIntensity: 1.2 } : {})} />;
39
+ }
40
+
41
+ // O fone: o arco, os deslizadores, as conchas com o anel de acento, as almofadas e o logo aceso.
42
+ function Fone({ cor, acento }) {
43
+ const R = 1.05;
44
+ const concha = (lado) => (
45
+ <group position={[lado * R, -0.12, 0]}>
46
+ <mesh rotation={[0, 0, Math.PI / 2]}>
47
+ <cylinderGeometry args={[0.44, 0.46, 0.26, 64]} />
48
+ <Material cor={cor} brilho={0.3} metal={0.35} />
49
+ </mesh>
50
+ <mesh position={[lado * 0.135, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
51
+ <torusGeometry args={[0.43, 0.035, 24, 96]} />
52
+ <Material cor={acento} brilho={0.18} metal={0.9} verniz={1} />
53
+ </mesh>
54
+ <mesh position={[lado * 0.14, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
55
+ <circleGeometry args={[0.16, 48]} />
56
+ <Material cor={acento} brilho={0.2} metal={0.2} emissivo={acento} />
57
+ </mesh>
58
+ <mesh position={[-lado * 0.16, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
59
+ <torusGeometry args={[0.3, 0.11, 32, 96]} />
60
+ <Material cor="#141416" brilho={0.85} metal={0} verniz={0} />
61
+ </mesh>
62
+ </group>
63
+ );
64
+ return (
65
+ <group>
66
+ <mesh>
67
+ <torusGeometry args={[R, 0.075, 32, 160, Math.PI]} />
68
+ <Material cor={cor} brilho={0.28} metal={0.4} />
69
+ </mesh>
70
+ <mesh position={[0, -0.06, 0]} scale={[1, 1, 1.6]}>
71
+ <torusGeometry args={[R - 0.07, 0.05, 24, 140, Math.PI]} />
72
+ <Material cor="#141416" brilho={0.9} metal={0} verniz={0} />
73
+ </mesh>
74
+ {[-1, 1].map((lado) => (
75
+ <mesh key={lado} position={[lado * R, 0.1, 0]}>
76
+ <boxGeometry args={[0.07, 0.34, 0.12]} />
77
+ <Material cor={acento} brilho={0.15} metal={0.95} verniz={1} />
78
+ </mesh>
79
+ ))}
80
+ {concha(-1)}
81
+ {concha(1)}
82
+ </group>
83
+ );
84
+ }
85
+
86
+ function Celular({ cor, acento }) {
87
+ return (
88
+ <group rotation={[0, 0, 0.05]}>
89
+ <mesh><boxGeometry args={[1.0, 2.05, 0.1]} /><Material cor={cor} brilho={0.22} metal={0.7} /></mesh>
90
+ <mesh position={[0, 0, 0.052]}><planeGeometry args={[0.92, 1.96]} /><Material cor="#0b0c10" brilho={0.1} metal={0} emissivo="#0d1830" /></mesh>
91
+ <mesh position={[0, 0.3, 0.054]}><circleGeometry args={[0.22, 48]} /><Material cor={acento} emissivo={acento} /></mesh>
92
+ <mesh position={[0.26, 0.78, -0.07]} rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[0.14, 0.14, 0.05, 48]} /><Material cor="#222" brilho={0.1} metal={0.9} /></mesh>
93
+ </group>
94
+ );
95
+ }
96
+
97
+ function Lata({ cor, acento }) {
98
+ return (
99
+ <group>
100
+ <mesh><cylinderGeometry args={[0.55, 0.55, 1.9, 96]} /><Material cor={cor} brilho={0.2} metal={0.85} /></mesh>
101
+ <mesh position={[0, 0.2, 0]}><cylinderGeometry args={[0.556, 0.556, 0.5, 96, 1, true]} /><Material cor={acento} brilho={0.3} metal={0.3} /></mesh>
102
+ <mesh position={[0, 0.97, 0]}><cylinderGeometry args={[0.5, 0.55, 0.06, 96]} /><Material cor="#c9ccd2" brilho={0.25} metal={1} /></mesh>
103
+ </group>
104
+ );
105
+ }
106
+
107
+ function Caixa({ cor, acento }) {
108
+ return (
109
+ <group>
110
+ <mesh><boxGeometry args={[1.5, 1.5, 1.5]} /><Material cor={cor} brilho={0.3} metal={0.3} /></mesh>
111
+ <mesh position={[0, 0, 0.752]}><planeGeometry args={[1.0, 0.18]} /><Material cor={acento} emissivo={acento} /></mesh>
112
+ </group>
113
+ );
114
+ }
115
+
116
+ const MODELOS = { fone: Fone, headset: Fone, celular: Celular, phone: Celular, lata: Lata, garrafa: Lata, caixa: Caixa };
117
+
118
+ export function Produto3D({ el, t }) {
119
+ const W = el.largura || 1920, H = el.altura || 1080;
120
+ const t0 = el.t || 0;
121
+ const tl = Math.max(0, t - t0);
122
+ const k = saida(tl / 0.9);
123
+ const Modelo = MODELOS[el.modelo] || Caixa;
124
+ const cor = el.cor || '#1b1c20';
125
+ const acento = el.corAcento || '#ff5a1f';
126
+ const giro = (el.giro0 ?? -0.9) + (1 - k) * 1.4 + tl * (el.giro ?? 0.35);
127
+ const escala = (el.escala || 1) * (0.72 + 0.28 * k);
128
+ const flutua = Math.sin(tl * 1.3) * 0.05;
129
+ return (
130
+ <div style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: W, height: H, opacity: t >= t0 ? Math.min(1, tl / 0.25) : 0 }}>
131
+ <ThreeCanvas width={W} height={H} camera={{ position: [0, 0.35, 4.4], fov: el.fov || 32 }} gl={{ antialias: true, preserveDrawingBuffer: true }}>
132
+ <Ambiente forca={0.8} />
133
+ <hemisphereLight args={['#ffffff', '#202020', 0.5]} />
134
+ <ambientLight intensity={0.35} />
135
+ <directionalLight position={[3, 4, 5]} intensity={3.2} />
136
+ <directionalLight position={[-2, 3, 4]} intensity={1.1} />
137
+ <directionalLight position={[-4, 1.5, -3]} intensity={3.2} color={acento} />
138
+ <pointLight position={[0, -2, 3]} intensity={0.8} />
139
+ <group position={[0, flutua - 0.05, 0]} rotation={[0.18 + (1 - k) * 0.3, giro, 0]} scale={[escala, escala, escala]}>
140
+ <Modelo cor={cor} acento={acento} />
141
+ </group>
142
+ {/* O chão. O gerativo dispensa (semChao): o disco virava uma faixa escura atrás do texto. */}
143
+ {!el.semChao && (
144
+ <mesh position={[0, -1.25, 0]} rotation={[-Math.PI / 2, 0, 0]}>
145
+ <circleGeometry args={[2.4, 64]} />
146
+ <meshStandardMaterial color="#0a0a0c" roughness={0.35} metalness={0.6} transparent opacity={0.85} />
147
+ </mesh>
148
+ )}
149
+ </ThreeCanvas>
150
+ </div>
151
+ );
152
+ }
153
+
154
+ /* ── OS ÍCONES 3D ─────────────────────────────────────────────────────────
155
+ * "A gente não tem ícone 3D" (30/09). Nada de emoji-3D de banco: cada ícone
156
+ * é geometria daqui — forma 2D extrudada com chanfro (raio, coração,
157
+ * estrela, escudo, balão, play, check, seta, engrenagem) ou primitivas
158
+ * compostas (bateria, cadeado, relógio, barras, fone, celular, sol, onda) —
159
+ * no material e na paleta do vídeo (argila, verniz ou metal), com a luz de
160
+ * recorte na cor de acento.
161
+ *
162
+ * {"tipo":"kit","peca":"icone3d","icone":"raio","x":0,"y":0,"tamanho":360,
163
+ * "material":"argila","cor":"#efe8da","cor2":"#7a1f1f","luz":"#7a1f1f","t":0} */
164
+ function forma(pts, curvas) {
165
+ const sh = new THREE.Shape();
166
+ pts.forEach(([x, y], i) => (i ? sh.lineTo(x, y) : sh.moveTo(x, y)));
167
+ if (curvas) curvas(sh);
168
+ sh.closePath();
169
+ return sh;
170
+ }
171
+ const FORMAS = {
172
+ raio: () => forma([[0.15, 1], [-0.55, -0.1], [-0.05, -0.1], [-0.25, -1], [0.55, 0.15], [0.05, 0.15], [0.35, 1]]),
173
+ estrela: () => forma(Array.from({ length: 10 }, (_, i) => { const a = Math.PI / 2 + (i * Math.PI) / 5, r = i % 2 ? 0.42 : 1; return [Math.cos(a) * r, Math.sin(a) * r]; })),
174
+ play: () => forma([[-0.55, 0.85], [0.85, 0], [-0.55, -0.85]]),
175
+ check: () => forma([[-0.95, 0.05], [-0.6, 0.4], [-0.25, 0.05], [0.6, 0.9], [0.95, 0.55], [-0.25, -0.65]]),
176
+ seta: () => forma([[-0.9, 0.22], [0.2, 0.22], [0.2, 0.62], [0.95, 0], [0.2, -0.62], [0.2, -0.22], [-0.9, -0.22]]),
177
+ coracao: () => {
178
+ const sh = new THREE.Shape();
179
+ sh.moveTo(0, -0.9);
180
+ sh.bezierCurveTo(-0.2, -0.65, -1, -0.2, -1, 0.3);
181
+ sh.bezierCurveTo(-1, 0.75, -0.6, 0.95, -0.35, 0.95);
182
+ sh.bezierCurveTo(-0.15, 0.95, 0, 0.8, 0, 0.62);
183
+ sh.bezierCurveTo(0, 0.8, 0.15, 0.95, 0.35, 0.95);
184
+ sh.bezierCurveTo(0.6, 0.95, 1, 0.75, 1, 0.3);
185
+ sh.bezierCurveTo(1, -0.2, 0.2, -0.65, 0, -0.9);
186
+ return sh;
187
+ },
188
+ escudo: () => {
189
+ const sh = new THREE.Shape();
190
+ sh.moveTo(0, 1); sh.lineTo(0.85, 0.7); sh.lineTo(0.8, 0);
191
+ sh.bezierCurveTo(0.75, -0.55, 0.35, -0.85, 0, -1);
192
+ sh.bezierCurveTo(-0.35, -0.85, -0.75, -0.55, -0.8, 0);
193
+ sh.lineTo(-0.85, 0.7); sh.closePath();
194
+ return sh;
195
+ },
196
+ balao: () => {
197
+ const sh = new THREE.Shape();
198
+ const r = 0.3, x0 = -0.95, x1 = 0.95, y0 = -0.45, y1 = 0.8;
199
+ sh.moveTo(x0 + r, y1); sh.lineTo(x1 - r, y1); sh.quadraticCurveTo(x1, y1, x1, y1 - r); sh.lineTo(x1, y0 + r);
200
+ sh.quadraticCurveTo(x1, y0, x1 - r, y0); sh.lineTo(-0.15, y0); sh.lineTo(-0.55, -0.95); sh.lineTo(-0.5, y0);
201
+ sh.lineTo(x0 + r, y0); sh.quadraticCurveTo(x0, y0, x0, y0 + r); sh.lineTo(x0, y1 - r); sh.quadraticCurveTo(x0, y1, x0 + r, y1);
202
+ return sh;
203
+ },
204
+ engrenagem: () => {
205
+ const n = 8, pts = [];
206
+ for (let i = 0; i < n * 4; i++) { const a = (i / (n * 4)) * Math.PI * 2, r = i % 4 < 2 ? 1 : 0.74; pts.push([Math.cos(a) * r, Math.sin(a) * r]); }
207
+ const sh = forma(pts);
208
+ const furo = new THREE.Path(); furo.absarc(0, 0, 0.34, 0, Math.PI * 2, true); sh.holes.push(furo);
209
+ return sh;
210
+ },
211
+ };
212
+
213
+ function Mat({ tipo, cor }) {
214
+ // Metal escovado, bala (verniz) ou argila — os três refletem a sala (Ambiente).
215
+ if (tipo === 'metal') return <meshPhysicalMaterial color={cor} roughness={0.32} metalness={0.85} clearcoat={0.4} envMapIntensity={1.2} />;
216
+ if (tipo === 'verniz') return <meshPhysicalMaterial color={cor} roughness={0.18} metalness={0} clearcoat={1} clearcoatRoughness={0.08} sheen={0.3} />;
217
+ return <meshPhysicalMaterial color={cor} roughness={0.72} metalness={0} clearcoat={0.15} />; // argila: fosca, macia
218
+ }
219
+
220
+ function Extrudado({ nome, m, cor }) {
221
+ const geo = useMemo(() => new THREE.ExtrudeGeometry(FORMAS[nome](), { depth: 0.42, bevelEnabled: true, bevelThickness: 0.12, bevelSize: 0.08, bevelSegments: 4, curveSegments: 18 }), [nome]);
222
+ return <mesh geometry={geo} position={[0, 0, -0.27]}><Mat tipo={m} cor={cor} /></mesh>;
223
+ }
224
+
225
+ function Composto({ nome, m, cor, cor2, tl }) {
226
+ const caixa = (pos, tam, c = cor, rot) => <mesh position={pos} rotation={rot}><boxGeometry args={tam} /><Mat tipo={m} cor={c} /></mesh>;
227
+ switch (nome) {
228
+ case 'bateria': return (
229
+ <group>
230
+ {caixa([0, 0, 0], [1.7, 0.95, 0.5])}
231
+ {caixa([0.95, 0, 0], [0.16, 0.4, 0.3])}
232
+ {[0, 1, 2].map((i) => caixa([-0.52 + i * 0.52, 0, 0.27], [0.36, 0.62, 0.08], i <= Math.floor(tl * 4) % 3 + 0.5 ? cor2 : cor))}
233
+ </group>
234
+ );
235
+ case 'cadeado': return (
236
+ <group>
237
+ {caixa([0, -0.3, 0], [1.3, 1.0, 0.5])}
238
+ <mesh position={[0, 0.25, 0]}><torusGeometry args={[0.42, 0.11, 20, 48, Math.PI]} /><Mat tipo={m} cor={cor2} /></mesh>
239
+ <mesh position={[0, -0.3, 0.27]} rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[0.1, 0.1, 0.06, 24]} /><Mat tipo={m} cor={cor2} /></mesh>
240
+ </group>
241
+ );
242
+ case 'relogio': return (
243
+ <group>
244
+ <mesh rotation={[Math.PI / 2, 0, 0]}><cylinderGeometry args={[1, 1, 0.32, 64]} /><Mat tipo={m} cor={cor} /></mesh>
245
+ <mesh position={[0, 0, 0.17]}><torusGeometry args={[0.98, 0.07, 16, 64]} /><Mat tipo={m} cor={cor2} /></mesh>
246
+ <group position={[0, 0, 0.2]} rotation={[0, 0, -tl * 2.2]}>{caixa([0, 0.33, 0], [0.09, 0.66, 0.05], cor2)}</group>
247
+ <group position={[0, 0, 0.22]} rotation={[0, 0, -tl * 0.4 - 1]}>{caixa([0, 0.22, 0], [0.12, 0.44, 0.05], cor2)}</group>
248
+ </group>
249
+ );
250
+ case 'barras': return (
251
+ <group>{[0.55, 0.95, 1.5].map((h, i) => caixa([-0.6 + i * 0.6, -0.75 + (h * Math.min(1, tl * 2.4 - i * 0.25 > 0 ? Math.min(1, tl * 2.4 - i * 0.25) : 0.05)) / 2, 0],
252
+ [0.42, Math.max(0.05, h * Math.min(1, Math.max(0.05, tl * 2.4 - i * 0.25))), 0.42], i === 2 ? cor2 : cor))}</group>
253
+ );
254
+ case 'fone': return (
255
+ <group>
256
+ <mesh><torusGeometry args={[0.85, 0.11, 20, 64, Math.PI]} /><Mat tipo={m} cor={cor} /></mesh>
257
+ {[-1, 1].map((l) => <mesh key={l} position={[l * 0.85, -0.25, 0]} rotation={[0, 0, Math.PI / 2]}><cylinderGeometry args={[0.36, 0.36, 0.3, 40]} /><Mat tipo={m} cor={cor2} /></mesh>)}
258
+ </group>
259
+ );
260
+ case 'celular': return (
261
+ <group>
262
+ {caixa([0, 0, 0], [1.05, 1.9, 0.16])}
263
+ {caixa([0, 0.02, 0.085], [0.9, 1.65, 0.02], cor2)}
264
+ </group>
265
+ );
266
+ case 'sol': return (
267
+ <group rotation={[0, 0, tl * 0.5]}>
268
+ <mesh><sphereGeometry args={[0.52, 48, 32]} /><Mat tipo={m} cor={cor2} /></mesh>
269
+ {Array.from({ length: 8 }, (_, i) => <mesh key={i} position={[Math.cos(i * Math.PI / 4) * 0.85, Math.sin(i * Math.PI / 4) * 0.85, 0]} rotation={[0, 0, i * Math.PI / 4 - Math.PI / 2]}>
270
+ <coneGeometry args={[0.13, 0.36, 20]} /><Mat tipo={m} cor={cor} /></mesh>)}
271
+ </group>
272
+ );
273
+ case 'onda': return (
274
+ <group>{[0.5, 1.1, 1.7, 1.1, 0.5].map((h, i) => { const k = 0.55 + 0.45 * Math.abs(Math.sin(tl * 5 + i * 1.1)); return caixa([-0.9 + i * 0.45, 0, 0], [0.26, h * k, 0.26], i === 2 ? cor2 : cor); })}</group>
275
+ );
276
+ default: return null;
277
+ }
278
+ }
279
+
280
+ export const ICONES = [...Object.keys(FORMAS), 'bateria', 'cadeado', 'relogio', 'barras', 'fone', 'celular', 'sol', 'onda'];
281
+
282
+ export function Icone3D({ el, t }) {
283
+ const tam = Math.round(el.tamanho || 360);
284
+ const tl = Math.max(0, t - (el.t || 0));
285
+ // Entra com mola que passa um pouco (o ícone tem peso) e gira até de frente.
286
+ const k = 1 - Math.exp(-7 * tl) * Math.cos(9 * tl);
287
+ const giro = (1 - saida(tl / 0.8)) * -1.6 + Math.sin(tl * 1.1) * 0.35;
288
+ const m = el.material || 'argila';
289
+ return (
290
+ <div style={{ position: 'absolute', left: el.x || 0, top: el.y || 0, width: tam, height: tam, opacity: t >= (el.t || 0) ? Math.min(1, tl / 0.12) : 0 }}>
291
+ {/* A sombra de contato: o objeto pousa, não flutua solto. */}
292
+ <div style={{ position: 'absolute', left: '18%', width: '64%', top: '84%', height: '10%', borderRadius: '50%', background: 'radial-gradient(ellipse, rgba(0,0,0,0.42) 0%, transparent 70%)', transform: 'scale(' + (0.8 + 0.2 * k).toFixed(3) + ')' }} />
293
+ <ThreeCanvas width={tam} height={tam} camera={{ position: [0, 0, 5.2], fov: 30 }} gl={{ antialias: true, preserveDrawingBuffer: true, alpha: true }}>
294
+ <Ambiente forca={1.1} />
295
+ <hemisphereLight args={['#ffffff', '#3a3a3a', 0.6]} />
296
+ <ambientLight intensity={0.25} />
297
+ <directionalLight position={[2.5, 3, 4]} intensity={2.6} />
298
+ <directionalLight position={[-3, -1, -2]} intensity={2.4} color={el.luz || '#ffffff'} />
299
+ <pointLight position={[0, 0, 3]} intensity={0.5} />
300
+ <group rotation={[0.25 + Math.sin(tl * 0.8) * 0.08, giro, Math.sin(tl * 0.6) * 0.06]} scale={[k, k, k]} position={[0, Math.sin(tl * 1.4) * 0.06, 0]}>
301
+ {FORMAS[el.icone] ? <Extrudado nome={el.icone} m={m} cor={el.cor || '#eeeeee'} /> : <Composto nome={el.icone} m={m} cor={el.cor || '#eeeeee'} cor2={el.cor2 || el.cor || '#ff5a1f'} tl={tl} />}
302
+ </group>
303
+ </ThreeCanvas>
304
+ </div>
305
+ );
306
+ }
307
+
308
+ export const TRES = { produto3d: Produto3D, icone3d: Icone3D };