@7ots/cli 0.1.1 → 0.1.4

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 (51) hide show
  1. package/.env.example +17 -1
  2. package/cli/7ots.mjs +144 -36
  3. package/cli/lib/assistant.mjs +347 -0
  4. package/cli/lib/brain.mjs +10 -7
  5. package/cli/lib/config.mjs +8 -1
  6. package/cli/lib/i18n.mjs +84 -11
  7. package/cli/lib/install.mjs +29 -1
  8. package/cli/lib/pet-core.mjs +3 -2
  9. package/cli/lib/pet-server.mjs +133 -13
  10. package/cli/lib/prompts.mjs +199 -7
  11. package/cli/lib/terminal.mjs +27 -6
  12. package/cli/pet/electron/main.cjs +96 -32
  13. package/cli/pet/pet.html +59 -6
  14. package/cli/pet/settings.html +430 -0
  15. package/dist/7ots.esm.js +370 -142
  16. package/dist/7ots.esm.js.map +4 -4
  17. package/dist/7ots.iife.js +370 -142
  18. package/dist/7ots.iife.js.map +4 -4
  19. package/llms.txt +198 -6
  20. package/package.json +1 -1
  21. package/server/channels/apuchat.mjs +8 -5
  22. package/server/channels/apumail.mjs +13 -3
  23. package/server/channels/index.mjs +5 -3
  24. package/server/platform/apuchat.mjs +266 -0
  25. package/server/platform/auth.mjs +39 -10
  26. package/server/platform/connect.mjs +347 -0
  27. package/server/platform/db.mjs +55 -0
  28. package/server/platform/look.mjs +20 -0
  29. package/server/platform/orquesta.mjs +499 -0
  30. package/server/platform/routes.mjs +57 -8
  31. package/server/platform/runtime.mjs +75 -15
  32. package/server/platform/store.mjs +130 -9
  33. package/server/server.mjs +14 -3
  34. package/src/avatar/AvatarStage.js +54 -9
  35. package/src/avatar/three.js +62 -0
  36. package/src/character/AvatarEditor.js +174 -50
  37. package/src/character/Character.js +164 -26
  38. package/src/character/motion.js +95 -34
  39. package/src/character/parts.js +43 -12
  40. package/src/character/skin.js +412 -0
  41. package/src/character/styles.js +1429 -0
  42. package/src/character3d/Character3D.js +1693 -0
  43. package/src/character3d/body.js +154 -0
  44. package/src/character3d/fx.js +323 -0
  45. package/src/character3d/index.js +281 -0
  46. package/src/character3d/materials.js +429 -0
  47. package/src/character3d/stage.js +187 -0
  48. package/src/i18n/messages/character.js +18 -42
  49. package/src/i18n/messages/platform.js +135 -0
  50. package/src/identity/schema.js +16 -8
  51. package/src/index.js +1 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Cuerpo 3D a partir del MISMO contorno de 16 puntos del personaje 2D.
3
+ *
4
+ * El contorno (Catmull-Rom, igual que splinePath) se pasa a una tabla polar R(θ) alrededor del
5
+ * centro del cuerpo. Una esfera unitaria se deforma así: una dirección (dx,dy,dz) va a
6
+ * (dx·R(θ), cy + dy·R(θ), dz·D) con θ = ángulo de (dx,dy). En el ecuador (dz=0) la silueta
7
+ * frontal es exactamente el contorno 2D; hacia delante y atrás se redondea como un peluche.
8
+ * La profundidad D se suaviza hacia el centro de la cara para que no haya pliegues ahí, y se
9
+ * limita en las puntas (orejas de gato, picos) para que sean más planas.
10
+ *
11
+ * Unidades: 1 unidad 3D = 100 unidades del viewBox 2D. El origen está en la base del cuerpo.
12
+ */
13
+
14
+ import * as THREE from 'three';
15
+ import { mergeVertices } from 'three/addons/utils/BufferGeometryUtils.js';
16
+
17
+ export const U = 1 / 100;
18
+ const NB = 360; // muestras angulares de la tabla polar
19
+
20
+ /** Contorno denso (mismas Bézier que splinePath en Character.js). */
21
+ export function densify(pts, per = 12) {
22
+ const N = pts.length;
23
+ const P = (i) => pts[(i + N) % N];
24
+ const out = [];
25
+ for (let i = 0; i < N; i++) {
26
+ const [p0, p1, p2, p3] = [P(i - 1), P(i), P(i + 1), P(i + 2)];
27
+ const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6];
28
+ const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6];
29
+ for (let k = 0; k < per; k++) {
30
+ const t = k / per;
31
+ const a = (1 - t) ** 3, b = 3 * (1 - t) ** 2 * t, c = 3 * (1 - t) * t * t, d = t ** 3;
32
+ out.push([a * p1[0] + b * c1[0] + c * c2[0] + d * p2[0], a * p1[1] + b * c1[1] + c * c2[1] + d * p2[1]]);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+
38
+ /** Tabla polar: distancia máxima del centro al contorno para NB ángulos (coordenadas 2D, y hacia abajo). */
39
+ function polarTable(poly, cx, cy) {
40
+ const R = new Float32Array(NB);
41
+ const n = poly.length;
42
+ for (let k = 0; k < NB; k++) {
43
+ const a = (k / NB) * Math.PI * 2;
44
+ const dx = Math.cos(a);
45
+ const dy = Math.sin(a);
46
+ let best = 0;
47
+ for (let i = 0; i < n; i++) {
48
+ const [x1, y1] = poly[i];
49
+ const [x2, y2] = poly[(i + 1) % n];
50
+ const ex = x2 - x1, ey = y2 - y1;
51
+ const den = dx * ey - dy * ex;
52
+ if (Math.abs(den) < 1e-9) continue;
53
+ const qx = x1 - cx, qy = y1 - cy;
54
+ const t = (qx * ey - qy * ex) / den; // a lo largo del rayo
55
+ const u = (qx * dy - qy * dx) / den; // a lo largo del segmento
56
+ if (t > 0 && u >= 0 && u <= 1 && t > best) best = t;
57
+ }
58
+ R[k] = best;
59
+ }
60
+ // suavizado circular ligero (quita escalones de la poligonal)
61
+ const S = new Float32Array(NB);
62
+ for (let k = 0; k < NB; k++) S[k] = (R[(k + NB - 1) % NB] + 2 * R[k] + R[(k + 1) % NB]) / 4;
63
+ return S;
64
+ }
65
+
66
+ const sampleTable = (T, ang) => {
67
+ let f = ((ang / (Math.PI * 2)) % 1 + 1) % 1 * NB;
68
+ const i = Math.floor(f);
69
+ f -= i;
70
+ return T[i % NB] * (1 - f) + T[(i + 1) % NB] * f;
71
+ };
72
+
73
+ const cache = new Map();
74
+
75
+ /**
76
+ * Geometría del cuerpo (cacheada por contorno y resolución).
77
+ * @param {number[][]} pts 16 puntos 2D
78
+ * @param {{w:number,h:number,x:number,y:number}} box caja 2D del contorno
79
+ * @param {'high'|'low'} quality
80
+ */
81
+ export function bodyGeometry(pts, box, quality = 'high') {
82
+ const key = `${quality}|${pts.map((p) => `${Math.round(p[0])},${Math.round(p[1])}`).join(' ')}`;
83
+ if (cache.has(key)) return cache.get(key);
84
+ const cx = box.x + box.w / 2;
85
+ const cy = box.y + box.h * 0.54;
86
+ const bottom = box.y + box.h;
87
+ const T = polarTable(densify(pts), cx, cy);
88
+ let mean = 0;
89
+ for (let k = 0; k < NB; k++) mean += T[k];
90
+ mean /= NB;
91
+ const depthK = 0.86;
92
+ const Dc = depthK * mean;
93
+
94
+ const [ws, hs] = quality === 'high' ? [96, 64] : [40, 26];
95
+ let g = new THREE.SphereGeometry(1, ws, hs);
96
+ g.deleteAttribute('uv');
97
+ g.deleteAttribute('normal');
98
+ g = mergeVertices(g, 1e-5); // sin costura: normales continuas
99
+ const pos = g.attributes.position;
100
+ const v = new THREE.Vector3();
101
+ for (let i = 0; i < pos.count; i++) {
102
+ v.fromBufferAttribute(pos, i).normalize();
103
+ const s2 = v.x * v.x + v.y * v.y;
104
+ const ang = Math.atan2(-v.y, v.x); // a coordenadas 2D (y hacia abajo)
105
+ const R = sampleTable(T, ang);
106
+ const Dt = depthK * Math.min(R, mean * 1.05);
107
+ const D = Dc + (Dt - Dc) * s2;
108
+ pos.setXYZ(i, v.x * R * U, (bottom - cy) * U + v.y * R * U, v.z * D * U);
109
+ }
110
+ // asiento: aplana un poco la base para que «se apoye» en el suelo
111
+ for (let i = 0; i < pos.count; i++) {
112
+ const y = pos.getY(i);
113
+ if (y < 0.03) pos.setY(i, 0.03 + (y - 0.03) * 0.35);
114
+ }
115
+ g.computeVertexNormals();
116
+ g.computeBoundingBox();
117
+ g.computeBoundingSphere();
118
+ g.userData.shared = true; // cacheada: no se libera con el personaje
119
+ const out = { geometry: g, table: T, cx, cy, bottom, mean, depth: Dc * U };
120
+ cache.set(key, out);
121
+ if (cache.size > 160) cache.delete(cache.keys().next().value);
122
+ return out;
123
+ }
124
+
125
+ /** Proyector a la superficie: (x,y) 3D → punto y normal en la cara frontal (o desde arriba). */
126
+ export function surfaceProbe(geometry) {
127
+ const mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ side: THREE.DoubleSide }));
128
+ const ray = new THREE.Raycaster();
129
+ const o = new THREE.Vector3();
130
+ const d = new THREE.Vector3();
131
+ const hit = (ox, oy, oz, dx, dy, dz) => {
132
+ o.set(ox, oy, oz);
133
+ d.set(dx, dy, dz).normalize();
134
+ ray.set(o, d);
135
+ const h = ray.intersectObject(mesh, false)[0];
136
+ return h ? { p: h.point.clone(), n: h.face.normal.clone() } : null;
137
+ };
138
+ return {
139
+ /** Punto frontal en (x,y). */
140
+ front(x, y) {
141
+ return hit(x, y, 10, 0, 0, -1) || { p: new THREE.Vector3(x, y, 0), n: new THREE.Vector3(0, 0, 1) };
142
+ },
143
+ /** Punto superior en (x,z). */
144
+ top(x, z = 0) {
145
+ return hit(x, 10, z, 0, -1, 0) || { p: new THREE.Vector3(x, 1, z), n: new THREE.Vector3(0, 1, 0) };
146
+ },
147
+ /** Semianchura y semiprofundidad a la altura y. */
148
+ section(y) {
149
+ const r = hit(10, y, 0, -1, 0, 0);
150
+ const f = hit(0, y, 10, 0, 0, -1);
151
+ return { w: r ? Math.abs(r.p.x) : 0.5, d: f ? Math.abs(f.p.z) : 0.4, z: f ? f.p.z : 0.4 };
152
+ },
153
+ };
154
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Efectos 3D de los ots: los mismos 16 de motion.js (EFFECTS) con las mismas posiciones
3
+ * relativas, como piezas de volumen baratas. Cada efecto es UN InstancedMesh por tipo de pieza
4
+ * (geometría unitaria y material compartidos), así una multitud entera cuesta pocos draws.
5
+ * Las piezas «desaparecen» encogiéndose (sin transparencias que ordenar).
6
+ *
7
+ * Coordenadas: las del personaje 3D (origen en la base, y hacia arriba). En 2D y crece hacia
8
+ * abajo desde la parte de arriba del cuerpo: y3 = H · (1 − fracción).
9
+ */
10
+
11
+ import * as THREE from 'three';
12
+ import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
13
+ import { EASE, clamp, seeded } from '../character/motion.js';
14
+ import { mat } from './materials.js';
15
+
16
+ const TAU = Math.PI * 2;
17
+ const DEG = Math.PI / 180;
18
+ const pop = (q, a = 0.2) => EASE.backOut(clamp(q / a));
19
+ const fade = (q, b = 0.25) => clamp((1 - q) / b);
20
+
21
+ // ── geometrías unitarias compartidas (perezosas) ──
22
+ const geos = {};
23
+ const ext = (shp, d = 0.25) => new THREE.ExtrudeGeometry(shp, { depth: d, bevelEnabled: true, bevelThickness: 0.08, bevelSize: 0.07, bevelSegments: 2, curveSegments: 8 }).center();
24
+ const clean = (g) => { for (const k of Object.keys(g.attributes)) if (k !== 'position' && k !== 'normal') g.deleteAttribute(k); return g; };
25
+ const merge = (...gs) => mergeGeometries(gs.map((g) => clean(g.index ? g.toNonIndexed() : g)));
26
+ const MAKERS = {
27
+ star: () => {
28
+ const s = new THREE.Shape();
29
+ s.moveTo(0, 1);
30
+ for (const [cx, cy, x, y] of [[0, 0, 1, 0], [0, 0, 0, -1], [0, 0, -1, 0], [0, 0, 0, 1]]) s.quadraticCurveTo(cx, cy, x, y);
31
+ return ext(s, 0.3);
32
+ },
33
+ heart: () => {
34
+ const s = new THREE.Shape();
35
+ s.moveTo(0, -0.9);
36
+ s.bezierCurveTo(-1.4, 0.1, -0.7, 1.05, 0, 0.35);
37
+ s.bezierCurveTo(0.7, 1.05, 1.4, 0.1, 0, -0.9);
38
+ return ext(s, 0.35);
39
+ },
40
+ drop: () => {
41
+ const p = [];
42
+ for (let i = 0; i <= 12; i++) {
43
+ const a = (i / 12) * Math.PI;
44
+ const r = Math.sin(a) * (a > Math.PI / 2 ? 1 : 0.35 + 0.65 * Math.sin(a));
45
+ p.push(new THREE.Vector2(Math.max(0.001, r * 0.8), Math.cos(a) * 1.2));
46
+ }
47
+ return new THREE.LatheGeometry(p, 14);
48
+ },
49
+ z: () => {
50
+ const s = new THREE.Shape();
51
+ s.moveTo(-1, 1); s.lineTo(1, 1); s.lineTo(1, 0.62); s.lineTo(-0.35, -0.62); s.lineTo(1, -0.62); s.lineTo(1, -1);
52
+ s.lineTo(-1, -1); s.lineTo(-1, -0.62); s.lineTo(0.35, 0.62); s.lineTo(-1, 0.62); s.lineTo(-1, 1);
53
+ return ext(s, 0.4);
54
+ },
55
+ note: () => merge(
56
+ new THREE.SphereGeometry(0.42, 12, 8).scale(1.25, 0.95, 0.7).rotateZ(0.35).translate(-0.3, -0.75, 0),
57
+ new THREE.CylinderGeometry(0.08, 0.08, 1.5, 6).translate(0.18, 0, 0),
58
+ new THREE.BoxGeometry(0.5, 0.18, 0.12).rotateZ(-0.5).translate(0.38, 0.6, 0),
59
+ ),
60
+ bang: () => merge(new THREE.CylinderGeometry(0.2, 0.12, 1.15, 12).translate(0, 0.62, 0), new THREE.SphereGeometry(0.17, 12, 8).translate(0, -0.12, 0)),
61
+ ask: () => {
62
+ const c = new THREE.CatmullRomCurve3([[-0.35, 0.75], [-0.25, 1.05], [0.15, 1.12], [0.42, 0.9], [0.35, 0.6], [0.05, 0.45], [0, 0.18]].map(([x, y]) => new THREE.Vector3(x, y, 0)));
63
+ return merge(new THREE.TubeGeometry(c, 24, 0.11, 8), new THREE.SphereGeometry(0.15, 12, 8).translate(0, -0.12, 0));
64
+ },
65
+ anger: () => {
66
+ const gs = [];
67
+ for (const [sx, sy] of [[1, -1], [1, 1], [-1, 1], [-1, -1]]) {
68
+ const c = new THREE.QuadraticBezierCurve3(new THREE.Vector3(0.25 * sx, 0.9 * sy, 0), new THREE.Vector3(0.25 * sx, 0.25 * sy, 0), new THREE.Vector3(0.9 * sx, 0.25 * sy, 0));
69
+ gs.push(new THREE.TubeGeometry(c, 10, 0.13, 6));
70
+ }
71
+ return merge(...gs);
72
+ },
73
+ cloud: () => merge(...[[0, 0, 0.55], [-0.5, -0.05, 0.4], [0.5, -0.02, 0.42], [-0.22, 0.28, 0.4], [0.25, 0.3, 0.38], [0, -0.2, 0.42]].map(([x, y, r]) => new THREE.SphereGeometry(r, 14, 10).scale(1, 1, 0.6).translate(x, y, 0))),
74
+ ball: () => new THREE.SphereGeometry(1, 14, 10),
75
+ ballHi: () => new THREE.SphereGeometry(1, 24, 16),
76
+ chip: () => new THREE.BoxGeometry(1, 0.5, 0.12),
77
+ };
78
+ export function glyph(id) {
79
+ let g = geos[id];
80
+ if (!g) { g = geos[id] = MAKERS[id](); g.userData.shared = true; }
81
+ return g;
82
+ }
83
+
84
+ let bubbleMat = null;
85
+ const bubble = () => bubbleMat || (bubbleMat = new THREE.MeshPhysicalMaterial({ color: '#bfe9ff', roughness: 0.02, clearcoat: 1, transparent: true, opacity: 0.4, depthWrite: false, envMapIntensity: 2, iridescence: 1 }));
86
+
87
+ const dummy = new THREE.Object3D();
88
+ /** InstancedMesh de una pieza; put() coloca la instancia i (escala 0 = oculta). */
89
+ function inst(geoId, material, n) {
90
+ const m = new THREE.InstancedMesh(glyph(geoId), material, n);
91
+ m.frustumCulled = false;
92
+ m.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
93
+ m.userData.instFx = true;
94
+ return m;
95
+ }
96
+ function put(m, i, x, y, z, s, rz = 0, rx = 0, ry = 0, sy = s) {
97
+ dummy.position.set(x, y, z);
98
+ dummy.rotation.set(rx, ry, rz);
99
+ dummy.scale.set(s, sy, s);
100
+ if (s <= 1e-4) dummy.scale.setScalar(1e-5);
101
+ dummy.updateMatrix();
102
+ m.setMatrixAt(i, dummy.matrix);
103
+ }
104
+ const done = (...ms) => { for (const m of ms) m.instanceMatrix.needsUpdate = true; };
105
+
106
+ /**
107
+ * Definiciones: { ms, loop?, rig? (va pegado a la cara), make(c, f) → Object3D, draw(c, f, p, age, a, rnd) }.
108
+ * c: { W, H, D, eyeY, r, sp, mouth: Vector3, low, tears: [[[x,y,z]…] por lado] }
109
+ */
110
+ export const FX3D = {
111
+ sparkles: {
112
+ ms: 1300,
113
+ make: (c, f) => (f.m = inst('star', mat('gloss', '#ffd84d'), 6)),
114
+ draw: (c, f, p, age, a, rnd) => {
115
+ for (let i = 0; i < 6; i++) {
116
+ const an = rnd() * TAU, d = 0.55 + rnd() * 0.25, zz = (rnd() - 0.3) * c.D;
117
+ const q = clamp((p - i * 0.08) / 0.5);
118
+ const s = c.W * 0.07 * (0.6 + rnd() * 0.6) * Math.sin(Math.PI * q) * a;
119
+ put(f.m, i, Math.cos(an) * c.W * d, c.H * 0.55 - Math.sin(an) * c.H * d * 0.85, zz, s, -age * 0.08 * DEG);
120
+ }
121
+ done(f.m);
122
+ },
123
+ },
124
+ sweat: {
125
+ ms: 1800,
126
+ make: (c, f) => (f.m = inst('drop', mat('gloss', '#8fd3ff'), 1)),
127
+ draw: (c, f, p, age, a) => {
128
+ put(f.m, 0, f.dir * c.W * 0.4, c.H * (0.8 - 0.08 * EASE.inOut(p)), c.D * 0.45, c.W * 0.05 * pop(p, 0.15) * Math.min(a, fade(p)), -f.dir * 12 * DEG);
129
+ done(f.m);
130
+ },
131
+ },
132
+ blush: {
133
+ ms: 2000,
134
+ make: () => null,
135
+ draw: (c, f, p, age, a) => { f.blush = Math.min(clamp(p / 0.15), fade(p)) * a; },
136
+ },
137
+ hearts: {
138
+ ms: 1900,
139
+ make: (c, f) => (f.m = inst('heart', mat('gloss', '#ff4d7a'), 3)),
140
+ draw: (c, f, p, age, a, rnd) => {
141
+ for (let i = 0; i < 3; i++) {
142
+ const q = clamp((p - i * 0.15) / 0.7);
143
+ const s = q <= 0 ? 0 : c.W * (0.07 + rnd() * 0.03) * pop(q) * fade(q, 0.3) * a;
144
+ put(f.m, i, (i - 1) * c.W * 0.28 + Math.sin(q * TAU + i) * c.W * 0.05, c.H * 0.88 + c.H * 0.5 * EASE.out(q), c.D * 0.3, s, Math.sin(q * 9 + i) * 0.25);
145
+ }
146
+ done(f.m);
147
+ },
148
+ },
149
+ zzz: {
150
+ ms: 2600,
151
+ loop: true,
152
+ make: (c, f) => (f.m = inst('z', mat('gloss', '#5b6ee1'), 3)),
153
+ draw: (c, f, p, age, a) => {
154
+ for (let i = 0; i < 3; i++) {
155
+ const q = (p + i / 3) % 1;
156
+ put(f.m, i, f.dir * (c.W * 0.32 + c.W * 0.25 * q), c.H * 0.88 + c.H * 0.45 * q, c.D * 0.2, c.W * (0.033 + 0.05 * q) * Math.sin(Math.PI * q) * a, f.dir * -0.2);
157
+ }
158
+ done(f.m);
159
+ },
160
+ },
161
+ exclaim: {
162
+ ms: 1100,
163
+ make: (c, f) => (f.m = inst('bang', mat('gloss', '#ff3b30'), 1)),
164
+ draw: (c, f, p, age, a) => {
165
+ put(f.m, 0, f.dir * c.W * 0.36, c.H * 1.04, c.D * 0.2, c.W * 0.24 * pop(p, 0.18) * Math.min(a, fade(p, 0.2)), Math.sin(age / 60) * 6 * fade(p, 0.6) * DEG);
166
+ done(f.m);
167
+ },
168
+ },
169
+ question: {
170
+ ms: 1500,
171
+ make: (c, f) => (f.m = inst('ask', mat('gloss', '#4f7cff'), 1)),
172
+ draw: (c, f, p, age, a) => {
173
+ put(f.m, 0, f.dir * c.W * 0.4, c.H * 1.02 + Math.sin(age / 200) * c.H * 0.015, c.D * 0.2, c.W * 0.24 * pop(p) * Math.min(a, fade(p, 0.2)), -f.dir * 12 * DEG);
174
+ done(f.m);
175
+ },
176
+ },
177
+ anger: {
178
+ ms: 1300,
179
+ make: (c, f) => (f.m = inst('anger', mat('gloss', '#ff2d2d'), 1)),
180
+ draw: (c, f, p, age, a) => {
181
+ put(f.m, 0, f.dir * c.W * 0.33, c.H * 0.86, c.D * 0.55, c.W * 0.075 * pop(p, 0.15) * (1 + 0.12 * Math.sin(age / 70)) * Math.min(a, fade(p)), 0, 0, f.dir * 0.5);
182
+ done(f.m);
183
+ },
184
+ },
185
+ steam: {
186
+ ms: 1500,
187
+ make: (c, f) => (f.m = inst('ball', mat('felt', '#eef0f3'), 6)),
188
+ draw: (c, f, p, age, a) => {
189
+ let k = 0;
190
+ for (const s of [-1, 1]) {
191
+ for (let i = 0; i < 3; i++) {
192
+ const q = clamp((p - i * 0.15) / 0.65);
193
+ const r = q <= 0 || q >= 1 ? 0 : c.W * (0.04 + 0.07 * q) * Math.min(1, (1 - q) * 3) * a;
194
+ put(f.m, k++, s * (c.W * 0.28 + c.W * 0.18 * q), c.H * 0.88 + c.H * 0.38 * q, 0, r);
195
+ }
196
+ }
197
+ done(f.m);
198
+ },
199
+ },
200
+ confetti: {
201
+ ms: 1900,
202
+ make: (c, f) => {
203
+ const n = c.low ? 12 : 22;
204
+ const m = (f.m = inst('chip', mat('plastic', '#ffffff'), n));
205
+ const cols = ['#ff4d7a', '#ffd84d', '#4dd2ff', '#7cff6b', '#b18cff', '#ff9f43'];
206
+ const col = new THREE.Color();
207
+ for (let i = 0; i < n; i++) m.setColorAt(i, col.set(cols[i % cols.length]));
208
+ return m;
209
+ },
210
+ draw: (c, f, p, age, a, rnd) => {
211
+ const tt = p * 1.9;
212
+ const n = f.m.count;
213
+ const s = c.W * 0.035 * Math.min(a, fade(p, 0.3));
214
+ for (let i = 0; i < n; i++) {
215
+ const an = -Math.PI / 2 + (rnd() - 0.5) * 2.2;
216
+ const v = c.H * (0.9 + rnd() * 0.8);
217
+ const vz = (rnd() - 0.4) * v * 0.6;
218
+ const sp = 0.3 + rnd() * 0.5;
219
+ const y = Math.max(0.005, c.H * 0.95 - Math.sin(an) * v * tt - c.H * 1.1 * tt * tt);
220
+ put(f.m, i, Math.cos(an) * v * tt * 0.7, y, vz * tt, s, age * sp * DEG + i, age * sp * 0.7 * DEG, i * 0.7);
221
+ }
222
+ done(f.m);
223
+ },
224
+ },
225
+ tears: {
226
+ ms: 2200,
227
+ rig: true,
228
+ make: (c, f) => (f.m = inst('drop', mat('gloss', '#8fd3ff'), 4)),
229
+ draw: (c, f, p, age, a) => {
230
+ let k = 0;
231
+ for (const path of c.tears) {
232
+ for (let i = 0; i < 2; i++) {
233
+ const q = ((p * 2 + i * 0.5) % 1) * (p < 0.9 ? 1 : 0);
234
+ const u = q * q * (path.length - 1);
235
+ const j = Math.min(path.length - 2, Math.floor(u));
236
+ const t = u - j;
237
+ const A = path[j], B = path[j + 1];
238
+ put(f.m, k++, A[0] + (B[0] - A[0]) * t, A[1] + (B[1] - A[1]) * t, A[2] + (B[2] - A[2]) * t, q <= 0 ? 0 : c.r * 0.28 * (1 - q) * a * 1.4);
239
+ }
240
+ }
241
+ done(f.m);
242
+ },
243
+ },
244
+ thought: {
245
+ ms: 2200,
246
+ make: (c, f) => {
247
+ f.m = inst('ball', mat('felt', '#ffffff'), 2);
248
+ f.c = inst('cloud', mat('felt', '#ffffff'), 1);
249
+ f.d = inst('ball', mat('felt', '#64748b'), 3);
250
+ const g = new THREE.Group();
251
+ g.add(f.m, f.c, f.d);
252
+ return g;
253
+ },
254
+ draw: (c, f, p, age, a) => {
255
+ const e = Math.min(a, fade(p, 0.15));
256
+ const pp = (q) => pop(clamp((p - q) / 0.4), 0.5);
257
+ const x = f.dir * c.W * 0.55, y = c.H * 1.22, z = c.D * 0.15;
258
+ put(f.m, 0, f.dir * c.W * 0.3, c.H * 0.98, z, c.W * 0.025 * pp(0) * e);
259
+ put(f.m, 1, f.dir * c.W * 0.4, c.H * 1.08, z, c.W * 0.042 * pp(0.08) * e);
260
+ const b = c.W * 0.36 * pp(0.16) * e;
261
+ put(f.c, 0, x, y, z, b);
262
+ for (let i = 0; i < 3; i++) put(f.d, i, x + (i - 1) * b * 0.32, y, z + b * 0.36, b * 0.07 * (0.6 + 0.6 * Math.max(0, Math.sin(age / 180 - i * 0.9))));
263
+ done(f.m, f.c, f.d);
264
+ },
265
+ },
266
+ notes: {
267
+ ms: 2200,
268
+ make: (c, f) => (f.m = inst('note', mat('gloss', '#8b5cf6'), 3)),
269
+ draw: (c, f, p, age, a) => {
270
+ for (let i = 0; i < 3; i++) {
271
+ const q = clamp((p - i * 0.18) / 0.65);
272
+ const s = i % 2 ? -1 : 1;
273
+ put(f.m, i, s * c.W * (0.42 + 0.1 * q) + Math.sin(q * TAU) * c.W * 0.03, c.H * 0.75 + c.H * 0.5 * q, c.D * 0.25, q <= 0 || q >= 1 ? 0 : c.W * 0.11 * Math.sin(Math.PI * q) * a, -s * 10 * DEG);
274
+ }
275
+ done(f.m);
276
+ },
277
+ },
278
+ sneeze: {
279
+ ms: 700,
280
+ make: (c, f) => (f.m = inst('ball', mat('gloss', '#9fd8ff'), c.low ? 6 : 10)),
281
+ draw: (c, f, p, age, a, rnd) => {
282
+ const q = EASE.out(p);
283
+ const mo = c.mouth;
284
+ for (let i = 0; i < f.m.count; i++) {
285
+ const an = (rnd() - 0.5) * 1.1, el = (rnd() - 0.5) * 0.8;
286
+ const d = c.W * (0.15 + rnd() * 0.45) * q;
287
+ put(f.m, i, mo.x + f.dir * c.W * 0.05 + Math.sin(an) * d, mo.y + Math.sin(el) * d * 0.5 - c.H * 0.15 * q * q, mo.z + Math.cos(an) * d, c.W * (0.012 + rnd() * 0.015) * (1 - p) * a);
288
+ }
289
+ done(f.m);
290
+ },
291
+ },
292
+ bubble: {
293
+ ms: 1000,
294
+ make: (c, f) => (f.m = inst('ballHi', bubble(), 1)),
295
+ draw: (c, f, p, age, a) => {
296
+ const q = EASE.out(clamp(p / 0.85));
297
+ const burst = clamp((p - 0.85) / 0.15);
298
+ const r = c.W * (0.03 + 0.05 * q) * (1 + burst * 0.4) * (1 - burst) * a;
299
+ put(f.m, 0, c.mouth.x + f.dir * c.W * 0.06 + Math.sin(p * 9) * c.W * 0.02, c.mouth.y + c.H * 0.3 * q, c.mouth.z + c.W * 0.08, r);
300
+ done(f.m);
301
+ },
302
+ },
303
+ dizzy: {
304
+ ms: 1600,
305
+ loop: true,
306
+ make: (c, f) => (f.m = inst('star', mat('gloss', '#ffd84d'), 3)),
307
+ draw: (c, f, p, age, a) => {
308
+ for (let i = 0; i < 3; i++) {
309
+ const an = p * TAU + (i * TAU) / 3;
310
+ put(f.m, i, Math.cos(an) * c.W * 0.42, c.H * 1.0 + Math.sin(an) * c.H * 0.04, Math.sin(an) * c.D * 1.1, c.W * 0.045 * a, age * 0.004, 0, an);
311
+ }
312
+ done(f.m);
313
+ },
314
+ },
315
+ };
316
+
317
+ /** Libera un efecto (los buffers de instancias; geometría y material son compartidos). */
318
+ export function disposeFx(o) {
319
+ o?.traverse?.((m) => { if (m.isInstancedMesh) m.dispose(); });
320
+ o?.removeFromParent();
321
+ }
322
+
323
+ export { seeded };