@7ots/cli 0.1.6 → 0.1.8
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/README.md +3 -3
- package/cli/7ots.mjs +13 -1
- package/cli/lib/account.mjs +152 -2
- package/cli/lib/assistant.mjs +107 -38
- package/cli/lib/brain.mjs +13 -2
- package/cli/lib/config.mjs +5 -1
- package/cli/lib/inferences.mjs +30 -6
- package/cli/lib/integrations.mjs +69 -5
- package/cli/lib/orquesta.mjs +44 -1
- package/cli/lib/pet-server.mjs +352 -11
- package/cli/lib/prompts.mjs +31 -17
- package/cli/lib/prowl.mjs +110 -0
- package/cli/pet/electron/director.cjs +37 -5
- package/cli/pet/electron/main.cjs +92 -10
- package/cli/pet/pet.html +149 -10
- package/cli/pet/settings.html +549 -43
- package/dist/7ots.esm.js +216 -137
- package/dist/7ots.esm.js.map +4 -4
- package/dist/7ots.iife.js +216 -137
- package/dist/7ots.iife.js.map +4 -4
- package/llms.txt +1 -1
- package/package.json +17 -14
- package/server/channels/agent.mjs +7 -1
- package/server/channels/index.mjs +3 -2
- package/server/platform/connect.mjs +4 -1
- package/server/platform/db.mjs +14 -1
- package/server/platform/device.mjs +101 -4
- package/server/platform/mods.mjs +222 -0
- package/server/platform/orquesta.mjs +78 -14
- package/server/platform/profile.mjs +161 -0
- package/server/platform/routes.mjs +39 -33
- package/server/platform/runtime.mjs +5 -3
- package/server/platform/store.mjs +1 -1
- package/server/platform/vm.mjs +688 -17
- package/server/server.mjs +12 -2
- package/server/tts.mjs +17 -4
- package/src/avatar/three.js +3 -2
- package/src/character/AvatarEditor.js +95 -7
- package/src/character/Character.js +116 -17
- package/src/character/TradingCard.js +226 -0
- package/src/character/mods/index.js +4 -0
- package/src/character/mods/orc.js +64 -0
- package/src/character/mods.js +251 -0
- package/src/character/more/acc-head.js +213 -0
- package/src/character/more/acc-rest.js +342 -0
- package/src/character/more/brows.js +39 -0
- package/src/character/more/cheeks.js +78 -0
- package/src/character/more/eyes.js +288 -0
- package/src/character/more/mouths.js +259 -0
- package/src/character/more/patterns.js +120 -0
- package/src/character/parts.js +100 -0
- package/src/character/skin.js +166 -1
- package/src/character/styles.js +1285 -9
- package/src/character3d/Character3D.js +253 -37
- package/src/character3d/index.js +7 -1
- package/src/character3d/model.js +367 -0
- package/src/i18n/messages/character.js +37 -22
- package/src/i18n/messages/platform.js +33 -0
- package/src/identity/random.js +17 -3
- package/src/identity/schema.js +10 -0
- package/src/index.js +1 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catálogo 2 (2026-10): piezas nuevas, añadidas al final de su lista en parts.js.
|
|
3
|
+
* Cada pieza: { id, label (es), en, pt, as3d (id existente más parecido para el visor 3D), … }.
|
|
4
|
+
*
|
|
5
|
+
* Ojos: p = { r, lx, ly (-1..1 mirada), side (-1 izq, 1 der), iris, ink, white, lid, mood }, centrados en (0,0).
|
|
6
|
+
* El lado interior (hacia la nariz) está en x = -side.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @param {object} h ayudantes de dibujo de parts.js */
|
|
10
|
+
export default ({ n, shade, shine, star, heartPath, look, pupil }) => {
|
|
11
|
+
const stroke = (c, w) => `stroke="${c}" stroke-width="${n(w)}" stroke-linecap="round" stroke-linejoin="round"`;
|
|
12
|
+
/** Forma blanca recortada: lo de dentro (pupila…) no se sale de `shape`. */
|
|
13
|
+
const clipped = (shape, fill, inner) => {
|
|
14
|
+
const id = `ec${Math.round(Math.random() * 1e9)}`;
|
|
15
|
+
return `<clipPath id="${id}"><path d="${shape}"/></clipPath><path d="${shape}" fill="${fill}"/><g clip-path="url(#${id})">${inner}</g>`;
|
|
16
|
+
};
|
|
17
|
+
const circ = (r, cx = 0, cy = 0) => `M${n(cx - r)} ${n(cy)}a${n(r)} ${n(r)} 0 1 0 ${n(r * 2)} 0a${n(r)} ${n(r)} 0 1 0 ${n(-r * 2)} 0Z`;
|
|
18
|
+
const poly = (pts) => `M${pts.map(([x, y]) => `${n(x)} ${n(y)}`).join('L')}Z`;
|
|
19
|
+
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
id: 'puppy', label: 'Perrito', en: 'Puppy', pt: 'Cachorrinho', as3d: 'kawaii',
|
|
23
|
+
draw: (p) => {
|
|
24
|
+
const r = p.r;
|
|
25
|
+
const [x, y] = look(p, 0.12);
|
|
26
|
+
return `<circle cx="${x}" cy="${y}" r="${n(r * 1.05)}" fill="${p.ink}"/><circle cx="${x}" cy="${n(+y + r * 0.38)}" r="${n(r * 0.55)}" fill="${p.iris}" opacity=".55"/>${shine(x - r * 0.34, y - r * 0.36, r * 0.36)}${shine(x + r * 0.4, y - r * 0.02, r * 0.15)}${shine(x - r * 0.02, y + r * 0.5, r * 0.1, 0.85)}`;
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'droopy', label: 'Caídos', en: 'Droopy', pt: 'Caídos', as3d: 'sad',
|
|
31
|
+
draw: (p) => {
|
|
32
|
+
const r = p.r;
|
|
33
|
+
const s = p.side;
|
|
34
|
+
return `${clipped(circ(r), p.white, `${pupil(p, r * 0.52, 0.3)}<path d="M${n(-s * (r + 1))} ${n(-r - 1)}H${n(s * (r + 1))}V${n(r * 0.1)}Q${n(-s * r * 0.1)} ${n(-r * 0.25)} ${n(-s * (r + 1))} ${n(-r * 0.55)}Z" fill="${p.lid}"/>`)}<path d="M${n(s * r)} ${n(r * 0.08)}Q${n(-s * r * 0.1)} ${n(-r * 0.25)} ${n(-s * r)} ${n(-r * 0.52)}" fill="none" ${stroke(p.ink, r * 0.16)}/>`;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{ id: 'moon', label: 'Lunitas', en: 'Crescents', pt: 'Luazinhas', as3d: 'happy', noBlink: true, draw: (p) => `<path d="M${n(-p.r * 0.95)} ${n(p.r * 0.4)}Q0 ${n(-p.r * 1.3)} ${n(p.r * 0.95)} ${n(p.r * 0.4)}Q0 ${n(-p.r * 0.35)} ${n(-p.r * 0.95)} ${n(p.r * 0.4)}Z" fill="${p.ink}" ${stroke(p.ink, p.r * 0.1)}/>` },
|
|
38
|
+
{ id: 'squint', label: 'Apretados', en: 'Squeezed', pt: 'Apertados', as3d: 'closed', noBlink: true, draw: (p) => { const d = -p.side; return `<path d="M${n(-d * p.r * 0.55)} ${n(-p.r * 0.7)}L${n(d * p.r * 0.6)} 0L${n(-d * p.r * 0.55)} ${n(p.r * 0.7)}" fill="none" ${stroke(p.ink, p.r * 0.3)}/>`; } },
|
|
39
|
+
{ id: 'uu', label: 'U u', en: 'U u', pt: 'U u', as3d: 'closed', noBlink: true, draw: (p) => `<path d="M${n(-p.r * 0.75)} ${n(-p.r * 0.45)}C${n(-p.r * 0.75)} ${n(p.r * 0.85)} ${n(p.r * 0.75)} ${n(p.r * 0.85)} ${n(p.r * 0.75)} ${n(-p.r * 0.45)}" fill="none" ${stroke(p.ink, p.r * 0.26)}/>` },
|
|
40
|
+
{
|
|
41
|
+
id: 'flower', label: 'Florecitas', en: 'Flowers', pt: 'Florzinhas', as3d: 'star',
|
|
42
|
+
draw: (p) => {
|
|
43
|
+
const r = p.r;
|
|
44
|
+
const [x, y] = look(p, 0.12);
|
|
45
|
+
const petals = [0, 72, 144, 216, 288].map((a) => `<ellipse cy="${n(-r * 0.58)}" rx="${n(r * 0.4)}" ry="${n(r * 0.52)}" transform="rotate(${a})" fill="${p.iris}" stroke="${shade(p.iris, -0.35)}" stroke-width="${n(r * 0.08)}"/>`).join('');
|
|
46
|
+
return `<g transform="translate(${x} ${y})">${petals}<circle r="${n(r * 0.42)}" fill="#facc15" stroke="${shade('#facc15', -0.35)}" stroke-width="${n(r * 0.08)}"/><circle r="${n(r * 0.24)}" fill="${p.ink}"/>${shine(-r * 0.1, -r * 0.12, r * 0.09)}</g>`;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'diamond', label: 'Rombos', en: 'Diamonds', pt: 'Losangos', as3d: 'star',
|
|
51
|
+
draw: (p) => {
|
|
52
|
+
const r = p.r;
|
|
53
|
+
const [x, y] = look(p, 0.1);
|
|
54
|
+
return `<g transform="translate(${x} ${y})"><path d="M0 ${n(-r * 1.15)}L${n(r * 0.85)} 0L0 ${n(r * 1.15)}L${n(-r * 0.85)} 0Z" fill="${p.iris}" ${stroke(shade(p.iris, -0.4), r * 0.12)}/><path d="M0 ${n(-r * 1.05)}L0 0L${n(-r * 0.76)} 0Z" fill="#fff" opacity=".3"/><path d="M0 ${n(r * 1.05)}L0 0L${n(r * 0.76)} 0Z" fill="#000" opacity=".15"/><path d="M0 ${n(-r * 0.45)}L${n(r * 0.32)} 0L0 ${n(r * 0.45)}L${n(-r * 0.32)} 0Z" fill="${p.ink}"/>${shine(-r * 0.22, -r * 0.35, r * 0.14)}</g>`;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'galaxy', label: 'Galaxia', en: 'Galaxy', pt: 'Galáxia', as3d: 'kawaii',
|
|
59
|
+
draw: (p) => {
|
|
60
|
+
const r = p.r;
|
|
61
|
+
const [x, y] = look(p, 0.1);
|
|
62
|
+
const tw = [[0.35, -0.2, 0.2], [-0.25, 0.4, 0.14], [0.45, 0.45, 0.1], [-0.5, -0.05, 0.08]].map(([a, b, k]) => `<path d="${star(r * k, 0.35, 4)}" transform="translate(${n(+x + a * r)} ${n(+y + b * r)})" fill="#fff"/>`).join('');
|
|
63
|
+
return `<circle cx="${x}" cy="${y}" r="${r}" fill="#1e1b4b"/><circle cx="${n(+x + r * 0.15)}" cy="${n(+y + r * 0.2)}" r="${n(r * 0.62)}" fill="${p.iris}" opacity=".55"/><circle cx="${n(+x - r * 0.2)}" cy="${n(+y + r * 0.35)}" r="${n(r * 0.36)}" fill="#f472b6" opacity=".45"/>${tw}${shine(x - r * 0.38, y - r * 0.4, r * 0.24)}`;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'fierce', label: 'Feroces', en: 'Fierce', pt: 'Ferozes', as3d: 'angry',
|
|
68
|
+
draw: (p) => {
|
|
69
|
+
const r = p.r;
|
|
70
|
+
const s = p.side;
|
|
71
|
+
const shape = `M${n(s * r * 1.2)} ${n(-r * 0.75)}L${n(-s * r * 1.1)} ${n(-r * 0.15)}Q${n(-s * r * 0.6)} ${n(r * 1.05)} ${n(s * r * 0.5)} ${n(r * 0.75)}Q${n(s * r * 1.3)} ${n(r * 0.4)} ${n(s * r * 1.2)} ${n(-r * 0.75)}Z`;
|
|
72
|
+
const [x, y] = look(p, 0.2);
|
|
73
|
+
return `${clipped(shape, p.white, `<circle cx="${n(+x + s * r * 0.05)}" cy="${n(+y + r * 0.2)}" r="${n(r * 0.55)}" fill="${p.iris}"/><circle cx="${n(+x + s * r * 0.05)}" cy="${n(+y + r * 0.2)}" r="${n(r * 0.3)}" fill="${p.ink}"/>${shine(+x + s * r * 0.05 - r * 0.15, +y + r * 0.04, r * 0.11)}`)}<path d="M${n(s * r * 1.3)} ${n(-r * 0.82)}L${n(-s * r * 1.15)} ${n(-r * 0.15)}" fill="none" ${stroke(p.ink, r * 0.26)}/>`;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{ id: 'stitched', label: 'Cosidos', en: 'Stitched', pt: 'Costurados', as3d: 'closed', noBlink: true, draw: (p) => `<path d="M${n(-p.r * 0.85)} 0H${n(p.r * 0.85)}" fill="none" ${stroke(p.ink, p.r * 0.18)}/><path d="${[-0.5, 0, 0.5].map((k) => `M${n(k * p.r - p.r * 0.08)} ${n(-p.r * 0.38)}L${n(k * p.r + p.r * 0.08)} ${n(p.r * 0.38)}`).join('')}" fill="none" ${stroke(p.ink, p.r * 0.14)}/>` },
|
|
77
|
+
{
|
|
78
|
+
id: 'shocked', label: 'En shock', en: 'Shocked', pt: 'Em choque', as3d: 'wide',
|
|
79
|
+
draw: (p) => {
|
|
80
|
+
const r = p.r;
|
|
81
|
+
const [x, y] = look(p, 0.45);
|
|
82
|
+
return `<ellipse rx="${n(r * 0.95)}" ry="${n(r * 1.3)}" fill="${p.white}" ${stroke(p.ink, r * 0.1)}/><circle cx="${x}" cy="${y}" r="${n(r * 0.17)}" fill="${p.ink}"/>`;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'lovestruck', label: 'Enamorados', en: 'Lovestruck', pt: 'Apaixonados', as3d: 'heart',
|
|
87
|
+
draw: (p) => {
|
|
88
|
+
const r = p.r;
|
|
89
|
+
const [x, y] = look(p, 0.1);
|
|
90
|
+
return `<circle cx="${x}" cy="${y}" r="${n(r * 0.98)}" fill="${p.ink}"/><path d="${heartPath(r * 0.42)}" transform="translate(${n(x - r * 0.3)} ${n(y - r * 0.28)})" fill="#fb7185"/><path d="${heartPath(r * 0.2)}" transform="translate(${n(+x + r * 0.4)} ${n(+y + r * 0.38)})" fill="#fda4af"/>${shine(x - r * 0.42, y - r * 0.44, r * 0.1)}`;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'ringed', label: 'Iris grande', en: 'Big iris', pt: 'Íris grande', as3d: 'round',
|
|
95
|
+
draw: (p) => {
|
|
96
|
+
const r = p.r;
|
|
97
|
+
const [x, y] = look(p, 0.3);
|
|
98
|
+
return `<circle r="${r}" fill="${p.white}"/><circle cx="${x}" cy="${y}" r="${n(r * 0.66)}" fill="${p.iris}" ${stroke(shade(p.iris, -0.45), r * 0.1)}/><circle cx="${x}" cy="${y}" r="${n(r * 0.42)}" fill="${shade(p.iris, 0.3)}" opacity=".5"/><circle cx="${x}" cy="${y}" r="${n(r * 0.3)}" fill="${p.ink}"/>${shine(x - r * 0.24, y - r * 0.28, r * 0.17)}`;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'smug', label: 'Satisfechos', en: 'Smug', pt: 'Convencidos', as3d: 'tired',
|
|
103
|
+
draw: (p) => {
|
|
104
|
+
const r = p.r;
|
|
105
|
+
return `${clipped(circ(r), p.white, `${pupil(p, r * 0.5, 0.25)}<path d="M${-r - 1} ${n(-r - 1)}H${r + 1}V${n(-r * 0.3)}H${-r - 1}Z" fill="${p.lid}"/><path d="M${-r - 1} ${r + 1}H${r + 1}V${n(r * 0.35)}Q0 ${n(-r * 0.05)} ${-r - 1} ${n(r * 0.35)}Z" fill="${p.lid}"/>`)}<path d="M${n(-r)} ${n(-r * 0.3)}H${n(r)}" fill="none" ${stroke(p.ink, r * 0.18)}/><path d="M${n(-r * 0.9)} ${n(r * 0.33)}Q0 ${n(-r * 0.02)} ${n(r * 0.9)} ${n(r * 0.33)}" fill="none" ${stroke(shade(p.lid, -0.3), r * 0.1)}/>`;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'snake', label: 'Serpiente', en: 'Snake', pt: 'Cobra', as3d: 'cat',
|
|
110
|
+
draw: (p) => {
|
|
111
|
+
const r = p.r;
|
|
112
|
+
const [x, y] = look(p, 0.3);
|
|
113
|
+
return `<ellipse rx="${n(r)}" ry="${n(r * 0.92)}" fill="${p.iris}" ${stroke(shade(p.iris, -0.45), r * 0.08)}/><ellipse cy="${n(r * 0.2)}" rx="${n(r * 0.66)}" ry="${n(r * 0.55)}" fill="${shade(p.iris, 0.35)}" opacity=".55"/><path d="M${x} ${n(+y - r * 0.85)}Q${n(+x + r * 0.26)} ${y} ${x} ${n(+y + r * 0.85)}Q${n(x - r * 0.26)} ${y} ${x} ${n(+y - r * 0.85)}Z" fill="${p.ink}"/>${shine(x - r * 0.42, y - r * 0.38, r * 0.14)}`;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'frog', label: 'Rana', en: 'Frog', pt: 'Sapo', as3d: 'round',
|
|
118
|
+
draw: (p) => {
|
|
119
|
+
const r = p.r;
|
|
120
|
+
const [x, y] = look(p, 0.28);
|
|
121
|
+
return `<circle cy="${n(-r * 0.1)}" r="${n(r * 1.18)}" fill="${p.lid}" ${stroke(shade(p.lid, -0.25), r * 0.08)}/><circle r="${n(r * 0.86)}" fill="${p.white}"/><ellipse cx="${x}" cy="${y}" rx="${n(r * 0.55)}" ry="${n(r * 0.28)}" fill="${p.ink}"/>${shine(x - r * 0.3, y - r * 0.32, r * 0.14)}`;
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'coin', label: 'Monedas', en: 'Coins', pt: 'Moedas', as3d: 'button',
|
|
126
|
+
draw: (p) => {
|
|
127
|
+
const r = p.r;
|
|
128
|
+
return `<circle r="${r}" fill="#facc15" ${stroke('#b45309', r * 0.12)}/><circle r="${n(r * 0.68)}" fill="none" stroke="#d97706" stroke-width="${n(r * 0.09)}"/><path d="${star(r * 0.42)}" fill="#f59e0b"/><path d="M${n(-r * 0.62)} ${n(-r * 0.3)}A${n(r * 0.7)} ${n(r * 0.7)} 0 0 1 ${n(-r * 0.1)} ${n(-r * 0.72)}" fill="none" ${stroke('#fff', r * 0.14)} opacity=".7"/>`;
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'pie', label: 'Dibujo animado', en: 'Cartoon', pt: 'Desenho animado', as3d: 'bean',
|
|
133
|
+
draw: (p) => {
|
|
134
|
+
const r = p.r;
|
|
135
|
+
const [x, y] = look(p, 0.22);
|
|
136
|
+
const shape = `M0 ${n(-r * 1.1)}C${n(r * 0.95)} ${n(-r * 1.1)} ${n(r * 0.95)} ${n(r * 1.1)} 0 ${n(r * 1.1)}C${n(-r * 0.95)} ${n(r * 1.1)} ${n(-r * 0.95)} ${n(-r * 1.1)} 0 ${n(-r * 1.1)}Z`;
|
|
137
|
+
return `${clipped(shape, p.white, `<ellipse cx="${x}" cy="${n(+y + r * 0.2)}" rx="${n(r * 0.4)}" ry="${n(r * 0.62)}" fill="${p.ink}"/><path d="M${x} ${n(+y + r * 0.05)}L${n(+x + r * 0.5)} ${n(+y - r * 0.3)}L${n(+x + r * 0.25)} ${n(+y - r * 0.65)}Z" fill="${p.white}"/>`)}<path d="${shape}" fill="none" ${stroke(p.ink, r * 0.1)}/>`;
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'sideeye', label: 'De reojo', en: 'Side-eye', pt: 'De rabo de olho', as3d: 'tired',
|
|
142
|
+
draw: (p) => {
|
|
143
|
+
const r = p.r;
|
|
144
|
+
const x = n(r * 0.42 + p.lx * r * 0.1);
|
|
145
|
+
return `${clipped(circ(r), p.white, `<circle cx="${x}" cy="${n(r * 0.1)}" r="${n(r * 0.5)}" fill="${p.ink}"/>${shine(x - r * 0.18, r * -0.08, r * 0.15)}<path d="M${-r - 1} ${n(-r - 1)}H${r + 1}V${n(-r * 0.25)}H${-r - 1}Z" fill="${p.lid}"/>`)}<path d="M${n(-r)} ${n(-r * 0.25)}H${n(r)}" fill="none" ${stroke(p.ink, r * 0.16)}/>`;
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'mischief', label: 'Traviesos', en: 'Mischievous', pt: 'Travessos', as3d: 'happy',
|
|
150
|
+
draw: (p) => {
|
|
151
|
+
const r = p.r;
|
|
152
|
+
const shape = `M${n(-r * 1.05)} ${n(r * 0.35)}Q0 ${n(-r * 1.55)} ${n(r * 1.05)} ${n(r * 0.35)}Q0 ${n(r * 0.05)} ${n(-r * 1.05)} ${n(r * 0.35)}Z`;
|
|
153
|
+
const [x, y] = look(p, 0.25);
|
|
154
|
+
return `${clipped(shape, p.white, `<circle cx="${x}" cy="${n(+y - r * 0.12)}" r="${n(r * 0.48)}" fill="${p.ink}"/>${shine(x - r * 0.16, y - r * 0.3, r * 0.14)}`)}<path d="M${n(-r * 1.05)} ${n(r * 0.35)}Q0 ${n(-r * 1.55)} ${n(r * 1.05)} ${n(r * 0.35)}" fill="none" ${stroke(p.ink, r * 0.2)}/><path d="M${n(-r * 0.9)} ${n(r * 0.3)}Q0 ${n(r * 0.05)} ${n(r * 0.9)} ${n(r * 0.3)}" fill="none" ${stroke(p.ink, r * 0.1)}/>`;
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'gem', label: 'Joyas', en: 'Gems', pt: 'Joias', as3d: 'star',
|
|
159
|
+
draw: (p) => {
|
|
160
|
+
const r = p.r;
|
|
161
|
+
const [x, y] = look(p, 0.1);
|
|
162
|
+
const hex = (k) => Array.from({ length: 6 }, (_, i) => [Math.cos(Math.PI / 6 + (i * Math.PI) / 3) * r * k, Math.sin(Math.PI / 6 + (i * Math.PI) / 3) * r * k]);
|
|
163
|
+
const o = hex(1.05);
|
|
164
|
+
const m = hex(0.5);
|
|
165
|
+
const facets = o.map((a, i) => { const b = o[(i + 1) % 6]; return `<path d="${poly([a, b, m[(i + 1) % 6], m[i]])}" fill="${i >= 3 ? shade(p.iris, 0.35 - (i - 3) * 0.12) : shade(p.iris, -0.1 - i * 0.08)}"/>`; }).join('');
|
|
166
|
+
return `<g transform="translate(${x} ${y})">${facets}<path d="${poly(m)}" fill="${shade(p.iris, 0.15)}"/><path d="${poly(o)}" fill="none" ${stroke(shade(p.iris, -0.5), r * 0.1)}/>${shine(-r * 0.15, -r * 0.2, r * 0.13)}</g>`;
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'rainbow', label: 'Arcoíris', en: 'Rainbow', pt: 'Arco-íris', as3d: 'hypno',
|
|
171
|
+
draw: (p) => {
|
|
172
|
+
const r = p.r;
|
|
173
|
+
const [x, y] = look(p, 0.25);
|
|
174
|
+
const rings = [['#ef4444', 0.72], ['#f59e0b', 0.6], ['#22c55e', 0.48], ['#3b82f6', 0.36]].map(([c, k]) => `<circle cx="${x}" cy="${y}" r="${n(r * k)}" fill="${c}"/>`).join('');
|
|
175
|
+
return `<circle r="${r}" fill="${p.white}"/>${rings}<circle cx="${x}" cy="${y}" r="${n(r * 0.22)}" fill="${p.ink}"/>${shine(x - r * 0.3, y - r * 0.32, r * 0.14)}`;
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'owl', label: 'Búho', en: 'Owl', pt: 'Coruja', as3d: 'wide',
|
|
180
|
+
draw: (p) => {
|
|
181
|
+
const r = p.r;
|
|
182
|
+
const [x, y] = look(p, 0.25);
|
|
183
|
+
const ray = Array.from({ length: 12 }, (_, i) => { const a = (i * Math.PI) / 6; return `M${n(Math.cos(a) * r * 0.98)} ${n(Math.sin(a) * r * 0.98)}L${n(Math.cos(a) * r * 1.18)} ${n(Math.sin(a) * r * 1.18)}`; }).join('');
|
|
184
|
+
return `<circle r="${n(r * 1.2)}" fill="${shade(p.lid, 0.35)}" ${stroke(shade(p.lid, -0.25), r * 0.07)}/><path d="${ray}" fill="none" ${stroke(shade(p.lid, -0.25), r * 0.08)}/><circle r="${n(r * 0.92)}" fill="${p.white}"/><circle cx="${x}" cy="${y}" r="${n(r * 0.68)}" fill="${p.iris}"/><circle cx="${x}" cy="${y}" r="${n(r * 0.4)}" fill="${p.ink}"/>${shine(x - r * 0.26, y - r * 0.28, r * 0.16)}`;
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'wobbly', label: 'Gelatina', en: 'Wobbly', pt: 'Gelatina', as3d: 'googly',
|
|
189
|
+
draw: (p) => {
|
|
190
|
+
const r = p.r;
|
|
191
|
+
const pts = Array.from({ length: 28 }, (_, i) => { const a = (i / 28) * Math.PI * 2; const k = 1 + 0.08 * Math.sin(a * 3 + p.side) + 0.05 * Math.cos(a * 5); return [Math.cos(a) * r * k, Math.sin(a) * r * k]; });
|
|
192
|
+
return `<path d="${poly(pts)}" fill="${p.white}" ${stroke(p.ink, r * 0.1)}/>${pupil(p, r * 0.48, 0.38)}`;
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'bubble', label: 'Burbuja', en: 'Bubble', pt: 'Bolha', as3d: 'glow',
|
|
197
|
+
draw: (p) => {
|
|
198
|
+
const r = p.r;
|
|
199
|
+
const [x, y] = look(p, 0.25);
|
|
200
|
+
return `<circle r="${n(r * 1.02)}" fill="${p.iris}" fill-opacity=".3" ${stroke(p.iris, r * 0.1)}/><circle cx="${x}" cy="${y}" r="${n(r * 0.42)}" fill="${shade(p.iris, -0.35)}"/><path d="M${n(-r * 0.72)} ${n(r * 0.05)}Q${n(-r * 0.72)} ${n(-r * 0.68)} ${n(-r * 0.02)} ${n(-r * 0.76)}" fill="none" ${stroke('#fff', r * 0.17)} opacity=".9"/>${shine(r * 0.45, r * 0.45, r * 0.1, 0.8)}`;
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{ id: 'dash', label: 'Rayitas', en: 'Dashes', pt: 'Risquinhos', as3d: 'closed', noBlink: true, draw: (p) => `<path d="M${n(-p.r * 0.72)} 0H${n(p.r * 0.72)}" fill="none" ${stroke(p.ink, p.r * 0.32)}/>` },
|
|
204
|
+
{ id: 'plus', label: 'Cruces', en: 'Plus', pt: 'Cruzes', as3d: 'cross', noBlink: true, draw: (p) => `<path d="M${n(-p.r * 0.62)} 0H${n(p.r * 0.62)}M0 ${n(-p.r * 0.62)}V${n(p.r * 0.62)}" fill="none" ${stroke(p.ink, p.r * 0.3)}/>` },
|
|
205
|
+
{
|
|
206
|
+
id: 'confused', label: 'Confundidos', en: 'Confused', pt: 'Confusos', as3d: 'round',
|
|
207
|
+
draw: (p) => {
|
|
208
|
+
const k = p.side < 0 ? 1.15 : 0.72;
|
|
209
|
+
const r = p.r * k;
|
|
210
|
+
const [x, y] = look({ ...p, r }, 0.35);
|
|
211
|
+
return `<circle cy="${n(p.side < 0 ? 0 : p.r * 0.15)}" r="${n(r)}" fill="${p.white}" ${stroke(p.ink, p.r * 0.08)}/><g transform="translate(0 ${n(p.side < 0 ? 0 : p.r * 0.15)})"><circle cx="${x}" cy="${y}" r="${n(r * 0.42)}" fill="${p.ink}"/>${shine(x - r * 0.15, y - r * 0.17, r * 0.13)}</g>`;
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: 'starry', label: 'Pupila estrella', en: 'Star pupils', pt: 'Pupila estrela', as3d: 'star',
|
|
216
|
+
draw: (p) => {
|
|
217
|
+
const r = p.r;
|
|
218
|
+
const [x, y] = look(p, 0.3);
|
|
219
|
+
return `<circle r="${r}" fill="${p.white}"/><path d="${star(r * 0.62, 0.48)}" transform="translate(${x} ${y})" fill="${p.iris}" ${stroke(shade(p.iris, -0.45), r * 0.08)}/>${shine(x - r * 0.12, y - r * 0.16, r * 0.1)}`;
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'heartpupil', label: 'Pupila corazón', en: 'Heart pupils', pt: 'Pupila coração', as3d: 'heart',
|
|
224
|
+
draw: (p) => {
|
|
225
|
+
const r = p.r;
|
|
226
|
+
const [x, y] = look(p, 0.3);
|
|
227
|
+
return `<circle r="${r}" fill="${p.white}"/><path d="${heartPath(r * 0.55)}" transform="translate(${x} ${n(+y + r * 0.05)})" fill="#ef3b5d"/>${shine(x - r * 0.2, y - r * 0.12, r * 0.1)}`;
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'welling', label: 'A punto de llorar', en: 'Welling up', pt: 'Quase chorando', as3d: 'teary',
|
|
232
|
+
draw: (p) => {
|
|
233
|
+
const r = p.r;
|
|
234
|
+
const [x, y] = look(p, 0.1);
|
|
235
|
+
const shape = circ(r * 0.98, +x, +y);
|
|
236
|
+
return `${clipped(shape, p.ink, `<path d="M${n(x - r)} ${n(+y + r * 0.3)}Q${n(x - r * 0.5)} ${n(+y + r * 0.15)} ${x} ${n(+y + r * 0.3)}T${n(+x + r)} ${n(+y + r * 0.3)}V${n(+y + r)}H${n(x - r)}Z" fill="#7dd3fc" opacity=".6"/>`)}${shine(x - r * 0.32, y - r * 0.36, r * 0.33)}${shine(+x + r * 0.32, y - r * 0.05, r * 0.14)}${shine(x - r * 0.3, +y + r * 0.55, r * 0.09, 0.9)}${shine(+x + r * 0.2, +y + r * 0.6, r * 0.07, 0.9)}`;
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: 'visor', label: 'Visor', en: 'Visor', pt: 'Viseira', as3d: 'led', single: true,
|
|
241
|
+
draw: (p) => {
|
|
242
|
+
const r = p.r;
|
|
243
|
+
const x = n(p.lx * r * 1.3);
|
|
244
|
+
return `<rect x="${n(-r * 2.7)}" y="${n(-r * 0.62)}" width="${n(r * 5.4)}" height="${n(r * 1.24)}" rx="${n(r * 0.62)}" fill="#0b1220" ${stroke('#475569', r * 0.1)}/><rect x="${n(x - r * 1.1)}" y="${n(-r * 0.36)}" width="${n(r * 2.2)}" height="${n(r * 0.72)}" rx="${n(r * 0.36)}" fill="${p.iris}" opacity=".35"/><rect x="${n(x - r * 0.8)}" y="${n(-r * 0.24)}" width="${n(r * 1.6)}" height="${n(r * 0.48)}" rx="${n(r * 0.24)}" fill="${p.iris}"/><rect x="${n(x - r * 0.6)}" y="${n(-r * 0.16)}" width="${n(r * 0.6)}" height="${n(r * 0.1)}" rx="1" fill="#fff" opacity=".7"/>`;
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'scanner', label: 'Escáner', en: 'Scanner', pt: 'Scanner', as3d: 'robot',
|
|
249
|
+
draw: (p) => {
|
|
250
|
+
const r = p.r;
|
|
251
|
+
const [x, y] = look(p, 0.2);
|
|
252
|
+
return `<circle r="${r}" fill="#0b1220" ${stroke('#64748b', r * 0.1)}/><circle r="${n(r * 0.62)}" fill="none" stroke="${p.iris}" stroke-width="${n(r * 0.2)}" opacity=".9"/><circle r="${n(r * 0.62)}" fill="none" stroke="${p.iris}" stroke-width="${n(r * 0.45)}" opacity=".2"/><circle cx="${x}" cy="${y}" r="${n(r * 0.22)}" fill="${p.iris}"/><circle cx="${n(x - r * 0.06)}" cy="${n(y - r * 0.06)}" r="${n(r * 0.08)}" fill="#fff"/>`;
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: 'triclops', label: 'Tres ojos', en: 'Three eyes', pt: 'Três olhos', as3d: 'cyclops', single: true,
|
|
257
|
+
draw: (p) => {
|
|
258
|
+
const r = p.r;
|
|
259
|
+
const one = (cx, cy, k) => { const rr = r * k; const [x, y] = look({ ...p, r: rr }, 0.3); return `<g transform="translate(${n(cx)} ${n(cy)})"><circle r="${n(rr)}" fill="${p.white}" ${stroke(shade(p.lid, -0.2), r * 0.08)}/><circle cx="${x}" cy="${y}" r="${n(rr * 0.58)}" fill="${p.iris}"/><circle cx="${x}" cy="${y}" r="${n(rr * 0.3)}" fill="${p.ink}"/>${shine(x - rr * 0.22, y - rr * 0.26, rr * 0.16)}</g>`; };
|
|
260
|
+
return one(-r * 1.75, r * 0.4, 0.88) + one(r * 1.75, r * 0.4, 0.88) + one(0, -r * 0.45, 1.15);
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'sewn', label: 'Botones cosidos', en: 'Sewn buttons', pt: 'Botões costurados', as3d: 'button', noBlink: true,
|
|
265
|
+
draw: (p) => {
|
|
266
|
+
const r = p.r;
|
|
267
|
+
const c = p.iris;
|
|
268
|
+
return `<circle r="${r}" fill="${c}" ${stroke(shade(c, -0.4), r * 0.1)}/><circle r="${n(r * 0.72)}" fill="none" stroke="${shade(c, -0.2)}" stroke-width="${n(r * 0.08)}"/>${[[-1, -1], [1, -1], [-1, 1], [1, 1]].map(([a, b]) => `<circle cx="${n(a * r * 0.3)}" cy="${n(b * r * 0.3)}" r="${n(r * 0.12)}" fill="${shade(c, -0.5)}"/>`).join('')}<path d="M${n(-r * 0.3)} ${n(-r * 0.3)}L${n(r * 0.3)} ${n(r * 0.3)}M${n(r * 0.3)} ${n(-r * 0.3)}L${n(-r * 0.3)} ${n(r * 0.3)}" fill="none" ${stroke('#f5f5f4', r * 0.12)}/>${shine(-r * 0.5, -r * 0.5, r * 0.12, 0.7)}`;
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'bored', label: 'Aburridos', en: 'Bored', pt: 'Entediados', as3d: 'tired',
|
|
273
|
+
draw: (p) => {
|
|
274
|
+
const r = p.r;
|
|
275
|
+
const x = n(p.lx * r * 0.35);
|
|
276
|
+
return `<path d="M${n(-r)} ${n(-r * 0.1)}H${n(r)}A${n(r)} ${n(r)} 0 0 1 ${n(-r)} ${n(-r * 0.1)}Z" fill="${p.white}"/><circle cx="${x}" cy="${n(r * 0.38)}" r="${n(r * 0.4)}" fill="${p.ink}"/>${shine(x - r * 0.14, r * 0.24, r * 0.12)}<path d="M${n(-r * 1.08)} ${n(-r * 0.1)}H${n(r * 1.08)}" fill="none" ${stroke(p.ink, r * 0.2)}/>`;
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: 'shoujo', label: 'Shoujo', en: 'Shoujo', pt: 'Shoujo', as3d: 'anime',
|
|
281
|
+
draw: (p) => {
|
|
282
|
+
const r = p.r;
|
|
283
|
+
const [x, y] = look(p, 0.12);
|
|
284
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${n(r * 0.85)}" ry="${n(r * 1.15)}" fill="${p.ink}"/><ellipse cx="${x}" cy="${n(+y + r * 0.38)}" rx="${n(r * 0.62)}" ry="${n(r * 0.6)}" fill="${p.iris}" opacity=".85"/><ellipse cx="${n(x - r * 0.28)}" cy="${n(y - r * 0.45)}" rx="${n(r * 0.3)}" ry="${n(r * 0.38)}" fill="#fff"/><path d="${star(r * 0.24, 0.3, 4)}" transform="translate(${n(+x + r * 0.32)} ${n(+y + r * 0.12)})" fill="#fff"/>${shine(x - r * 0.25, +y + r * 0.62, r * 0.08, 0.9)}`;
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catálogo 2 (2026-10): piezas nuevas, añadidas al final de su lista en parts.js.
|
|
3
|
+
* Cada pieza: { id, label (es), en, pt, as3d (id existente más parecido para el visor 3D), … }.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @param {object} h ayudantes de dibujo de parts.js */
|
|
7
|
+
export default (h) => {
|
|
8
|
+
const { n, shade, mouthShape, openMouth, line, closedOr, heartPath } = h;
|
|
9
|
+
// p = { w (media anchura), open 0..1, curve -1..1, ink, lip, tongue, teeth, lipColor }
|
|
10
|
+
|
|
11
|
+
/** Forma rellena de boca con su interior recortado y el borde encima. */
|
|
12
|
+
const clipped = (p, shape, inner, sw = Math.max(1.4, p.w * 0.1)) => {
|
|
13
|
+
const id = `m${Math.round(Math.random() * 1e9)}`;
|
|
14
|
+
return `<clipPath id="${id}"><path d="${shape}"/></clipPath><path d="${shape}" fill="${p.ink}"/><g clip-path="url(#${id})">${inner}</g><path d="${shape}" fill="none" stroke="${p.lip}" stroke-width="${n(sw)}" stroke-linejoin="round"/>`;
|
|
15
|
+
};
|
|
16
|
+
/** y de la línea de la sonrisa básica ('smile') en x. */
|
|
17
|
+
const smileY = (p, x) => {
|
|
18
|
+
const y0 = -p.w * 0.1 - p.curve * 3;
|
|
19
|
+
const yc = p.w * 0.75 + p.curve * p.w * 0.4;
|
|
20
|
+
const t = (x / p.w + 1) / 2;
|
|
21
|
+
return y0 * (1 - 2 * t * (1 - t)) + 2 * t * (1 - t) * yc;
|
|
22
|
+
};
|
|
23
|
+
const smileD = (p, k = 1) => `M${n(-p.w * k)} ${n(smileY(p, -p.w * k))}Q0 ${n(p.w * 0.75 + p.curve * p.w * 0.4)} ${n(p.w * k)} ${n(smileY(p, p.w * k))}`;
|
|
24
|
+
const smile = (p) => line(p, `M${n(-p.w)} ${n(-p.w * 0.1 - p.curve * 3)}Q0 ${n(p.w * 0.75 + p.curve * p.w * 0.4)} ${n(p.w)} ${n(-p.w * 0.1 - p.curve * 3)}`);
|
|
25
|
+
const fangAt = (p, x, y, s = 1) => `<path d="M${n(x - p.w * 0.11 * s)} ${n(y - 0.5)}L${n(x)} ${n(y + p.w * 0.3 * s)}L${n(x + p.w * 0.11 * s)} ${n(y - 0.5)}Z" fill="${p.teeth}" stroke="${p.lip}" stroke-width="1" stroke-linejoin="round"/>`;
|
|
26
|
+
const tongueTip = (p, x, y, s = 1) => `<path d="M${n(x - p.w * 0.17 * s)} ${n(y)}v${n(p.w * 0.14 * s)}a${n(p.w * 0.17 * s)} ${n(p.w * 0.17 * s)} 0 0 0 ${n(p.w * 0.34 * s)} 0v${n(-p.w * 0.14 * s)}Z" fill="${p.tongue}" stroke="${shade(p.tongue, -0.3)}" stroke-width="1.2"/><path d="M${n(x)} ${n(y + p.w * 0.04)}v${n(p.w * 0.14 * s)}" stroke="${shade(p.tongue, -0.3)}" stroke-width="1"/>`;
|
|
27
|
+
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
id: 'dgrin', label: 'Sonrisa D', en: 'Big D grin', pt: 'Sorrisão D', as3d: 'laugh',
|
|
31
|
+
draw: (p) => {
|
|
32
|
+
const w = p.w;
|
|
33
|
+
const hh = w * (0.7 + p.open * 0.45);
|
|
34
|
+
const shape = `M${n(-w)} ${n(-w * 0.05)}Q0 ${n(-w * 0.18)} ${n(w)} ${n(-w * 0.05)}C${n(w)} ${n(hh * 0.8)} ${n(w * 0.5)} ${n(hh)} 0 ${n(hh)}C${n(-w * 0.5)} ${n(hh)} ${n(-w)} ${n(hh * 0.8)} ${n(-w)} ${n(-w * 0.05)}Z`;
|
|
35
|
+
return clipped(p, shape, `<rect x="${n(-w)}" y="${n(-w * 0.3)}" width="${n(w * 2)}" height="${n(w * 0.5)}" fill="${p.teeth}"/><ellipse cy="${n(hh)}" rx="${n(w * 0.55)}" ry="${n(hh * 0.42)}" fill="${p.tongue}"/>`);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'shark', label: 'Tiburón', en: 'Shark', pt: 'Tubarão', as3d: 'monster',
|
|
40
|
+
draw: (p) => {
|
|
41
|
+
const w = p.w * 1.1;
|
|
42
|
+
const hh = p.w * (0.5 + p.open * 0.55);
|
|
43
|
+
const cv = Math.max(0.2, p.curve + 0.4);
|
|
44
|
+
const c = cv * w * 0.35;
|
|
45
|
+
const shape = mouthShape(w, hh, cv);
|
|
46
|
+
const vT = -0.2 * c - 0.075 * hh + p.w * 0.08;
|
|
47
|
+
const vB = hh + c * 0.4 - p.w * 0.1;
|
|
48
|
+
let top = `M${n(-w - 2)} ${n(-w)}H${n(w + 2)}V${n(vT)}`;
|
|
49
|
+
for (let i = 6; i >= 0; i--) top += `L${n(-w + (i * 2 * w) / 6)} ${n(vT)}${i ? `L${n(-w + ((i - 0.5) * 2 * w) / 6)} ${n(vT + p.w * 0.32)}` : ''}`;
|
|
50
|
+
let bot = `M${n(-w - 2)} ${n(hh + w)}H${n(w + 2)}V${n(vB)}`;
|
|
51
|
+
for (let i = 5; i >= 0; i--) bot += `L${n(-w * 0.85 + (i * 1.7 * w) / 5)} ${n(vB)}${i ? `L${n(-w * 0.85 + ((i - 0.5) * 1.7 * w) / 5)} ${n(vB - p.w * 0.24)}` : ''}`;
|
|
52
|
+
return clipped(p, shape, `<path d="${top}Z" fill="${p.teeth}"/><path d="${bot}Z" fill="${p.teeth}"/>`);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'fang', label: 'Un colmillo', en: 'Single fang', pt: 'Um caninho', as3d: 'vampire',
|
|
57
|
+
draw: (p) => {
|
|
58
|
+
if (p.open > 0.12) {
|
|
59
|
+
const w = p.w * 0.85;
|
|
60
|
+
return `${openMouth(p, { w, max: 0.85 })}${fangAt(p, w * 0.45, -p.curve * w * 0.3, 0.9)}`;
|
|
61
|
+
}
|
|
62
|
+
return `${fangAt(p, p.w * 0.4, smileY(p, p.w * 0.4), 1.35)}${smile(p)}`;
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'fangs', label: 'Colmillitos', en: 'Tiny fangs', pt: 'Caninhos', as3d: 'vampire',
|
|
67
|
+
draw: (p) => {
|
|
68
|
+
if (p.open > 0.12) return openMouth({ ...p, w: p.w * 0.9 }, { w: p.w * 0.9, max: 0.85, fangs: true });
|
|
69
|
+
return `${fangAt(p, -p.w * 0.38, smileY(p, -p.w * 0.38), 1.1)}${fangAt(p, p.w * 0.38, smileY(p, p.w * 0.38), 1.1)}${smile(p)}`;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'bunny', label: 'Conejito', en: 'Bunny', pt: 'Coelhinho', as3d: 'buck',
|
|
74
|
+
draw: (p) => {
|
|
75
|
+
const w = p.w;
|
|
76
|
+
const teeth = (y) => `<path d="M${n(-w * 0.24)} ${n(y)}h${n(w * 0.48)}v${n(w * 0.3)}q0 ${n(w * 0.1)} ${n(-w * 0.1)} ${n(w * 0.1)}h${n(-w * 0.28)}q${n(-w * 0.1)} 0 ${n(-w * 0.1)} ${n(-w * 0.1)}Z" fill="${p.teeth}" stroke="${p.lip}" stroke-width="1.2" stroke-linejoin="round"/><path d="M0 ${n(y)}v${n(w * 0.34)}" stroke="${p.lip}" stroke-width="1"/>`;
|
|
77
|
+
if (p.open > 0.12) return `${openMouth({ ...p, curve: Math.max(0.3, p.curve + 0.5) }, { w: w * 0.75, min: 0.1, max: 0.75, tongue: false })}${teeth(-w * 0.08)}`;
|
|
78
|
+
return `${teeth(w * 0.02)}${line(p, `M${n(-w * 0.6)} ${n(-w * 0.08)}Q${n(-w * 0.3)} ${n(w * 0.32)} 0 ${n(w * 0.02)}Q${n(w * 0.3)} ${n(w * 0.32)} ${n(w * 0.6)} ${n(-w * 0.08)}`, 0.14)}`;
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'zigzag', label: 'Zigzag', en: 'Zigzag', pt: 'Zigue-zague', as3d: 'wavy',
|
|
83
|
+
draw: closedOr((p) => line(p, `M${n(-p.w * 0.85)} ${n(-p.w * 0.08)}${Array.from({ length: 6 }, (_, i) => `L${n(-p.w * 0.85 + ((i + 1) * p.w * 1.7) / 6)} ${n(i % 2 ? -p.w * 0.08 : p.w * 0.16)}`).join('')}`, 0.14)),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'uwu', label: 'UwU', en: 'UwU', pt: 'UwU', as3d: 'cat',
|
|
87
|
+
draw: closedOr((p) => line(p, `M${n(-p.w * 0.5)} ${n(-p.w * 0.08)}Q${n(-p.w * 0.25)} ${n(p.w * 0.36)} 0 ${n(p.w * 0.02)}Q${n(p.w * 0.25)} ${n(p.w * 0.36)} ${n(p.w * 0.5)} ${n(-p.w * 0.08)}`, 0.13)),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'yawn', label: 'Bostezo', en: 'Yawn', pt: 'Bocejo', as3d: 'open',
|
|
91
|
+
draw: (p) => {
|
|
92
|
+
const rx = p.w * 0.5;
|
|
93
|
+
const ry = p.w * (0.5 + p.open * 0.35);
|
|
94
|
+
const cy = p.w * 0.25;
|
|
95
|
+
const shape = `M${n(-rx)} ${n(cy)}a${n(rx)} ${n(ry)} 0 1 0 ${n(rx * 2)} 0a${n(rx)} ${n(ry)} 0 1 0 ${n(-rx * 2)} 0Z`;
|
|
96
|
+
return clipped(p, shape, `<ellipse cy="${n(cy + ry * 0.95)}" rx="${n(rx * 0.75)}" ry="${n(ry * 0.45)}" fill="${p.tongue}"/><ellipse cy="${n(cy - ry * 0.7)}" rx="${n(rx * 0.12)}" ry="${n(ry * 0.22)}" fill="${shade(p.tongue, -0.15)}"/>`, 2);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'vee', label: 'Uve', en: 'Little v', pt: 'Vezinho', as3d: 'tiny',
|
|
101
|
+
draw: closedOr((p) => line(p, `M${n(-p.w * 0.36)} ${n(-p.w * 0.1)}L0 ${n(p.w * 0.22)}L${n(p.w * 0.36)} ${n(-p.w * 0.1)}`, 0.15)),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'dimples', label: 'Hoyuelos', en: 'Dimples', pt: 'Covinhas', as3d: 'smile',
|
|
105
|
+
draw: closedOr((p) => {
|
|
106
|
+
const k = 0.8;
|
|
107
|
+
const y = smileY(p, -p.w * k);
|
|
108
|
+
return `${line(p, smileD(p, k))}${[-1, 1].map((s) => `<path d="M${n(s * p.w * 0.9)} ${n(y - p.w * 0.12)}q${n(s * p.w * 0.12)} ${n(p.w * 0.1)} ${n(s * p.w * 0.02)} ${n(p.w * 0.26)}" stroke="${p.lip}" stroke-width="${n(Math.max(1.3, p.w * 0.11))}" fill="none" stroke-linecap="round"/>`).join('')}`;
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'bubblegum', label: 'Chicle', en: 'Bubblegum', pt: 'Chiclete', as3d: 'kiss',
|
|
113
|
+
draw: (p) => {
|
|
114
|
+
if (p.open > 0.12) return openMouth(p, { w: p.w * 0.6, max: 0.8 });
|
|
115
|
+
const r = p.w * 0.72;
|
|
116
|
+
return `<ellipse rx="${n(p.w * 0.22)}" ry="${n(p.w * 0.16)}" fill="#f472b6"/><circle cy="${n(r * 0.55)}" r="${n(r)}" fill="#f9a8d4" stroke="${shade('#f9a8d4', -0.3)}" stroke-width="1.5" opacity=".95"/><ellipse cx="${n(-r * 0.38)}" cy="${n(r * 0.2)}" rx="${n(r * 0.22)}" ry="${n(r * 0.14)}" transform="rotate(-35 ${n(-r * 0.38)} ${n(r * 0.2)})" fill="#fff" opacity=".75"/><circle cx="${n(r * 0.35)}" cy="${n(r * 0.95)}" r="${n(r * 0.07)}" fill="#fff" opacity=".6"/>`;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'tongueside', label: 'Lengua de lado', en: 'Side tongue', pt: 'Língua de lado', as3d: 'tongue',
|
|
121
|
+
draw: closedOr((p) => {
|
|
122
|
+
const x = p.w * 0.42;
|
|
123
|
+
const y = smileY(p, x);
|
|
124
|
+
return `<g transform="rotate(-14 ${n(x)} ${n(y)})">${tongueTip(p, x, y - p.w * 0.04, 1.3)}</g>${smile(p)}`;
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'lipbite', label: 'Mordiéndose el labio', en: 'Lip bite', pt: 'Mordendo o lábio', as3d: 'buck',
|
|
129
|
+
draw: closedOr((p) => {
|
|
130
|
+
const w = p.w;
|
|
131
|
+
return `<path d="M${n(-w * 0.32)} ${n(w * 0.32)}Q0 ${n(w * 0.48)} ${n(w * 0.32)} ${n(w * 0.32)}" stroke="${p.lip}" stroke-width="${n(Math.max(1.3, w * 0.1))}" fill="none" stroke-linecap="round" opacity=".45"/>${line(p, `M${n(-w * 0.75)} ${n(-w * 0.05)}Q0 ${n(w * 0.3)} ${n(w * 0.75)} ${n(-w * 0.05)}`, 0.15)}<path d="M${n(-w * 0.22)} ${n(w * 0.06)}h${n(w * 0.44)}v${n(w * 0.16)}q0 ${n(w * 0.08)} ${n(-w * 0.08)} ${n(w * 0.08)}h${n(-w * 0.28)}q${n(-w * 0.08)} 0 ${n(-w * 0.08)} ${n(-w * 0.08)}Z" fill="${p.teeth}" stroke="${p.lip}" stroke-width="1.1" stroke-linejoin="round"/><path d="M0 ${n(w * 0.06)}v${n(w * 0.24)}" stroke="${p.lip}" stroke-width="0.9"/>`;
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'heart', label: 'Corazón', en: 'Heart', pt: 'Coração', as3d: 'oh',
|
|
136
|
+
draw: (p) => {
|
|
137
|
+
const r = p.w * (0.45 + p.open * 0.3);
|
|
138
|
+
const id = `mh${Math.round(Math.random() * 1e9)}`;
|
|
139
|
+
const tf = `translate(0 ${n(r * 0.35)})`;
|
|
140
|
+
return `<clipPath id="${id}"><path d="${heartPath(r)}" transform="${tf}"/></clipPath><path d="${heartPath(r)}" transform="${tf}" fill="${p.ink}"/><g clip-path="url(#${id})"><ellipse cy="${n(r * 1.05)}" rx="${n(r * 0.55)}" ry="${n(r * 0.4)}" fill="${p.tongue}"/></g><path d="${heartPath(r)}" transform="${tf}" fill="none" stroke="${p.lip}" stroke-width="${n(Math.max(1.4, p.w * 0.1))}" stroke-linejoin="round"/>`;
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'stitch', label: 'Cosida', en: 'Stitched', pt: 'Costurada', as3d: 'flat',
|
|
145
|
+
draw: closedOr((p) => {
|
|
146
|
+
const w = p.w;
|
|
147
|
+
const y = (x) => w * 0.1 * (1 - (x / (w * 0.8)) ** 2);
|
|
148
|
+
return `${line(p, `M${n(-w * 0.8)} 0Q0 ${n(w * 0.2)} ${n(w * 0.8)} 0`, 0.13)}${[-0.55, -0.2, 0.2, 0.55].map((k) => `<path d="M${n(k * w - w * 0.05)} ${n(y(k * w) - w * 0.2)}L${n(k * w + w * 0.05)} ${n(y(k * w) + w * 0.2)}" stroke="${p.lip}" stroke-width="${n(Math.max(1.4, w * 0.1))}" stroke-linecap="round"/>`).join('')}`;
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'zipper', label: 'Cremallera', en: 'Zipper', pt: 'Zíper', as3d: 'flat',
|
|
153
|
+
draw: (p) => {
|
|
154
|
+
const w = p.w;
|
|
155
|
+
const tab = (x, y) => `<rect x="${n(x)}" y="${n(y - w * 0.06)}" width="${n(w * 0.2)}" height="${n(w * 0.4)}" rx="${n(w * 0.07)}" fill="#e2e8f0" stroke="${p.lip}" stroke-width="1.2"/><circle cx="${n(x + w * 0.1)}" cy="${n(y + w * 0.22)}" r="${n(w * 0.045)}" fill="${p.lip}"/>`;
|
|
156
|
+
if (p.open > 0.12) return `${openMouth(p, { w: w * 0.8, max: 0.8 })}${tab(w * 0.8, -w * 0.05)}`;
|
|
157
|
+
const ticks = Array.from({ length: 9 }, (_, i) => `<rect x="${n(-w * 0.78 + i * w * 0.19)}" y="${n(i % 2 ? 0 : -w * 0.13)}" width="${n(w * 0.1)}" height="${n(w * 0.13)}" rx="1" fill="#cbd5e1" stroke="${p.lip}" stroke-width=".8"/>`).join('');
|
|
158
|
+
return `<path d="M${n(-w * 0.82)} 0H${n(w * 0.82)}" stroke="${p.lip}" stroke-width="${n(Math.max(1.4, w * 0.08))}" stroke-linecap="round"/>${ticks}${tab(w * 0.82, -w * 0.04)}`;
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'equalizer', label: 'Ecualizador', en: 'Equalizer', pt: 'Equalizador', as3d: 'robot',
|
|
163
|
+
draw: (p) => {
|
|
164
|
+
const w = p.w;
|
|
165
|
+
const hh = w * 0.62;
|
|
166
|
+
const k = [0.35, 0.6, 0.85, 1, 0.85, 0.6, 0.35];
|
|
167
|
+
const bw = (w * 1.6) / 7;
|
|
168
|
+
return `<rect x="${n(-w)}" y="${n(-hh / 2)}" width="${n(w * 2)}" height="${n(hh)}" rx="${n(hh * 0.3)}" fill="#0b1220" stroke="#94a3b8" stroke-width="2"/>${k.map((v, i) => {
|
|
169
|
+
const bh = Math.max(1.5, (hh - 5) * (0.15 + v * (0.12 + p.open * 0.73)));
|
|
170
|
+
return `<rect x="${n(-w * 0.8 + i * bw + bw * 0.18)}" y="${n(-bh / 2)}" width="${n(bw * 0.64)}" height="${n(bh)}" rx="1" fill="${v > 0.9 && p.open > 0.5 ? '#facc15' : '#4ade80'}"/>`;
|
|
171
|
+
}).join('')}`;
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'frog', label: 'Rana', en: 'Froggy', pt: 'Sapinho', as3d: 'smile',
|
|
176
|
+
draw: (p) => {
|
|
177
|
+
const w = p.w * 1.35;
|
|
178
|
+
if (p.open > 0.12) return openMouth(p, { w, max: 0.6 });
|
|
179
|
+
return `${line(p, `M${n(-w)} ${n(-p.w * 0.15)}Q0 ${n(p.w * 0.5 + p.curve * p.w * 0.3)} ${n(w)} ${n(-p.w * 0.15)}`, 0.14)}${[-1, 1].map((s) => `<path d="M${n(s * (w - p.w * 0.1))} ${n(-p.w * 0.32)}l${n(s * p.w * 0.12)} ${n(p.w * 0.24)}" stroke="${p.lip}" stroke-width="${n(Math.max(1.4, p.w * 0.1))}" stroke-linecap="round"/>`).join('')}`;
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'meh', label: 'Desganada', en: 'Meh', pt: 'Desanimada', as3d: 'frown',
|
|
184
|
+
draw: closedOr((p) => line(p, `M${n(-p.w * 0.7)} ${n(p.w * 0.02)}Q${n(p.w * 0.1)} ${n(-p.w * 0.14)} ${n(p.w * 0.7)} ${n(p.w * 0.3)}`, 0.16)),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'wail', label: 'Llorando', en: 'Wail', pt: 'Berreiro', as3d: 'frown',
|
|
188
|
+
draw: (p) => openMouth({ ...p, curve: Math.min(-0.5, p.curve - 0.8) }, { min: 0.5, max: 1 }),
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'gasp', label: 'Sorpresa', en: 'Gasp', pt: 'Susto', as3d: 'oh',
|
|
192
|
+
draw: (p) => {
|
|
193
|
+
const w = p.w * 0.55;
|
|
194
|
+
const t = p.w * (0.32 + p.open * 0.4);
|
|
195
|
+
const b = p.w * 0.3;
|
|
196
|
+
const shape = `M${n(-w)} ${n(b - p.w * 0.06)}Q${n(-w)} ${n(-t)} 0 ${n(-t)}Q${n(w)} ${n(-t)} ${n(w)} ${n(b - p.w * 0.06)}Q${n(w)} ${n(b)} ${n(w - p.w * 0.08)} ${n(b)}H${n(-w + p.w * 0.08)}Q${n(-w)} ${n(b)} ${n(-w)} ${n(b - p.w * 0.06)}Z`;
|
|
197
|
+
return clipped(p, shape, `<ellipse cy="${n(b)}" rx="${n(w * 0.75)}" ry="${n(p.w * 0.2)}" fill="${p.tongue}"/>`, 2);
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'yum', label: 'Relamiéndose', en: 'Yum', pt: 'Lambendo', as3d: 'tongue',
|
|
202
|
+
draw: closedOr((p) => {
|
|
203
|
+
const x = -p.w * 0.62;
|
|
204
|
+
const y = smileY(p, x);
|
|
205
|
+
return `${smile(p)}<g transform="rotate(-40 ${n(x)} ${n(y)})">${tongueTip(p, x, y - p.w * 0.18, 1.1)}</g>`;
|
|
206
|
+
}),
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'triangle', label: 'Triangular', en: 'Triangle', pt: 'Triangular', as3d: 'open',
|
|
210
|
+
draw: (p) => {
|
|
211
|
+
const w = p.w * 0.6;
|
|
212
|
+
const hh = p.w * (0.5 + p.open * 0.55);
|
|
213
|
+
const top = -p.w * 0.15;
|
|
214
|
+
const shape = `M${n(-w)} ${n(top)}H${n(w)}L0 ${n(top + hh)}Z`;
|
|
215
|
+
return clipped(p, shape, `<ellipse cy="${n(top + hh * 0.85)}" rx="${n(w * 0.5)}" ry="${n(hh * 0.3)}" fill="${p.tongue}"/>`, Math.max(2, p.w * 0.14));
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'smallgrin', label: 'Sonrisita', en: 'Little grin', pt: 'Sorrisinho', as3d: 'grin',
|
|
220
|
+
draw: (p) => openMouth({ ...p, curve: Math.max(0.45, p.curve + 0.6) }, { w: p.w * 0.6, min: 0.45, teeth: true }),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'squiggle', label: 'Ondulada', en: 'Squiggle', pt: 'Ondulada', as3d: 'wavy',
|
|
224
|
+
draw: closedOr((p) => {
|
|
225
|
+
let d = '';
|
|
226
|
+
for (let i = 0; i <= 24; i++) {
|
|
227
|
+
const u = -1 + i / 12;
|
|
228
|
+
const x = u * p.w * 0.9;
|
|
229
|
+
const y = p.w * (0.28 + p.curve * 0.1) * (1 - u * u) - p.w * 0.08 + Math.sin(u * Math.PI * 3) * p.w * 0.07;
|
|
230
|
+
d += `${i ? 'L' : 'M'}${n(x)} ${n(y)}`;
|
|
231
|
+
}
|
|
232
|
+
return line(p, d, 0.14);
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'duckbill', label: 'Pico de pato', en: 'Duck bill', pt: 'Bico de pato', as3d: 'beak',
|
|
237
|
+
draw: (p) => {
|
|
238
|
+
const w = p.w;
|
|
239
|
+
const o = p.open * w * 0.5;
|
|
240
|
+
const st = `stroke="${shade('#f97316', -0.35)}" stroke-width="1.2"`;
|
|
241
|
+
return `${o > 1 ? `<ellipse cy="${n(w * 0.08)}" rx="${n(w * 0.7)}" ry="${n(o * 0.6 + 1)}" fill="${p.ink}"/>` : ''}<path d="M${n(-w * 0.78)} ${n(w * 0.12 + o / 2)}Q${n(-w * 0.85)} ${n(w * 0.4 + o / 2)} 0 ${n(w * 0.42 + o / 2)}Q${n(w * 0.85)} ${n(w * 0.4 + o / 2)} ${n(w * 0.78)} ${n(w * 0.12 + o / 2)}Z" fill="#ea580c" ${st}/><path d="M${n(-w * 0.9)} ${n(w * 0.1 - o / 2)}Q${n(-w * 0.95)} ${n(-w * 0.32 - o / 2)} 0 ${n(-w * 0.34 - o / 2)}Q${n(w * 0.95)} ${n(-w * 0.32 - o / 2)} ${n(w * 0.9)} ${n(w * 0.1 - o / 2)}Q0 ${n(w * 0.26 - o / 2)} ${n(-w * 0.9)} ${n(w * 0.1 - o / 2)}Z" fill="#fb923c" ${st}/><ellipse cx="${n(-w * 0.2)}" cy="${n(-w * 0.16 - o / 2)}" rx="${n(w * 0.06)}" ry="${n(w * 0.035)}" fill="${shade('#f97316', -0.45)}"/><ellipse cx="${n(w * 0.2)}" cy="${n(-w * 0.16 - o / 2)}" rx="${n(w * 0.06)}" ry="${n(w * 0.035)}" fill="${shade('#f97316', -0.45)}"/><ellipse cx="${n(-w * 0.45)}" cy="${n(-w * 0.2 - o / 2)}" rx="${n(w * 0.16)}" ry="${n(w * 0.05)}" fill="#fff" opacity=".45"/>`;
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'blep', label: 'Blep', en: 'Blep', pt: 'Blep', as3d: 'tongue',
|
|
246
|
+
draw: closedOr((p) => `${tongueTip(p, 0, p.w * 0.02, 0.95)}${line(p, `M${n(-p.w * 0.38)} ${n(-p.w * 0.02)}Q0 ${n(p.w * 0.12)} ${n(p.w * 0.38)} ${n(-p.w * 0.02)}`, 0.14)}`),
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'scream', label: 'Grito', en: 'Scream', pt: 'Grito', as3d: 'open',
|
|
250
|
+
draw: (p) => {
|
|
251
|
+
const rx = p.w * 0.32;
|
|
252
|
+
const ry = p.w * (0.62 + p.open * 0.3);
|
|
253
|
+
const cy = p.w * 0.3;
|
|
254
|
+
const shape = `M0 ${n(cy - ry)}C${n(rx * 1.4)} ${n(cy - ry)} ${n(rx * 0.9)} ${n(cy + ry)} 0 ${n(cy + ry)}C${n(-rx * 0.9)} ${n(cy + ry)} ${n(-rx * 1.4)} ${n(cy - ry)} 0 ${n(cy - ry)}Z`;
|
|
255
|
+
return clipped(p, shape, `<ellipse cy="${n(cy + ry * 0.9)}" rx="${n(rx * 0.7)}" ry="${n(ry * 0.3)}" fill="${p.tongue}"/>`, 2);
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
};
|