@7ots/cli 0.1.7 → 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 +1 -1
- package/cli/7ots.mjs +13 -1
- package/cli/lib/account.mjs +152 -2
- package/cli/lib/assistant.mjs +106 -41
- package/cli/lib/brain.mjs +13 -2
- package/cli/lib/config.mjs +3 -0
- 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 +344 -11
- 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 +545 -42
- 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/package.json +1 -1
- 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 +9 -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 +38 -33
- package/server/platform/runtime.mjs +3 -1
- package/server/platform/store.mjs +1 -1
- package/server/platform/vm.mjs +370 -91
- package/server/server.mjs +12 -2
- 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 +90 -0
- package/src/character/skin.js +166 -1
- package/src/character/styles.js +1285 -9
- package/src/character3d/Character3D.js +148 -35
- package/src/character3d/index.js +7 -1
- package/src/character3d/model.js +367 -0
- package/src/i18n/messages/character.js +34 -22
- package/src/i18n/messages/platform.js +15 -0
- package/src/identity/random.js +17 -3
- package/src/identity/schema.js +10 -0
- package/src/index.js +1 -0
|
@@ -0,0 +1,120 @@
|
|
|
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, star, heartPath } = h;
|
|
9
|
+
// p = { box {x,y,w,h}, color, eyeY, r } — se recortan con la silueta del cuerpo
|
|
10
|
+
const X = (p, a) => p.box.x + a * p.box.w;
|
|
11
|
+
const Y = (p, b) => p.box.y + b * p.box.h;
|
|
12
|
+
/** Banda horizontal con borde ondulado (de y hacia abajo si down, hacia arriba si no). */
|
|
13
|
+
const wavyBand = (p, y, down, amp, waves) => {
|
|
14
|
+
const { x, w, h } = p.box;
|
|
15
|
+
const step = (w + 20) / (waves * 2);
|
|
16
|
+
let d = `M${n(x - 10)} ${n(y)}`;
|
|
17
|
+
for (let i = 0; i < waves * 2; i++) d += `q${n(step / 2)} ${n(i % 2 ? amp : -amp)} ${n(step)} 0`;
|
|
18
|
+
return `${d}V${n(down ? y + h : y - h)}H${n(x - 10)}Z`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
23
|
+
id: 'polka', label: 'Lunares', en: 'Polka dots', pt: 'Bolinhas', as3d: 'spots',
|
|
24
|
+
draw: (p) => {
|
|
25
|
+
const s = p.box.w / 5;
|
|
26
|
+
const out = [];
|
|
27
|
+
for (let j = 0; j * s < p.box.h + s; j++) for (let i = -1; i * s < p.box.w + s; i++) out.push(`<circle cx="${n(p.box.x + i * s + (j % 2 ? s / 2 : 0))}" cy="${n(p.box.y + j * s + s * 0.3)}" r="${n(s * 0.2)}" fill="${p.color}"/>`);
|
|
28
|
+
return out.join('');
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'zigzag', label: 'Zigzag', en: 'Zigzag', pt: 'Zigue-zague', as3d: 'stripes',
|
|
33
|
+
draw: (p) => [0.3, 0.55, 0.8].map((b) => {
|
|
34
|
+
const k = p.box.w / 8;
|
|
35
|
+
let d = `M${n(p.box.x - k)} ${n(Y(p, b))}`;
|
|
36
|
+
for (let i = 0; i < 10; i++) d += `l${n(k)} ${n(i % 2 ? k * 0.7 : -k * 0.7)}`;
|
|
37
|
+
return `<path d="${d}" stroke="${p.color}" stroke-width="${n(p.box.h / 16)}" fill="none" stroke-linejoin="round"/>`;
|
|
38
|
+
}).join(''),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'checker', label: 'Cuadros', en: 'Checkers', pt: 'Xadrez', as3d: 'stripes',
|
|
42
|
+
draw: (p) => {
|
|
43
|
+
const s = p.box.w / 6;
|
|
44
|
+
const out = [];
|
|
45
|
+
for (let j = 0; j * s < p.box.h; j++) for (let i = 0; i * s < p.box.w; i++) if ((i + j) % 2) out.push(`<rect x="${n(p.box.x + i * s)}" y="${n(p.box.y + j * s)}" width="${n(s)}" height="${n(s)}" fill="${p.color}"/>`);
|
|
46
|
+
return out.join('');
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'argyle', label: 'Rombos', en: 'Argyle', pt: 'Losangos', as3d: 'spots',
|
|
51
|
+
draw: (p) => {
|
|
52
|
+
const s = p.box.w / 4;
|
|
53
|
+
const hh = s * 1.3;
|
|
54
|
+
const out = [];
|
|
55
|
+
for (let j = 0; j * hh < p.box.h + hh; j++) for (let i = 0; i * s <= p.box.w + s; i++) if ((i + j) % 2 === 0) out.push(`<path d="M${n(p.box.x + i * s)} ${n(p.box.y + j * hh - hh / 2)}l${n(s / 2)} ${n(hh / 2)}l${n(-s / 2)} ${n(hh / 2)}l${n(-s / 2)} ${n(-hh / 2)}Z" fill="${p.color}"/>`);
|
|
56
|
+
return `${out.join('')}<g stroke="${shade(p.color, -0.2)}" stroke-width="1.2" opacity=".55">${Array.from({ length: 8 }, (_, i) => `<path d="M${n(p.box.x + (i - 3) * s)} ${n(p.box.y)}l${n(p.box.h / 1.3)} ${n(p.box.h)}M${n(p.box.x + (i + 1) * s)} ${n(p.box.y)}l${n(-p.box.h / 1.3)} ${n(p.box.h)}"/>`).join('')}</g>`;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'waves', label: 'Olas', en: 'Waves', pt: 'Ondas', as3d: 'stripes',
|
|
61
|
+
draw: (p) => [0.28, 0.5, 0.72, 0.94].map((b) => {
|
|
62
|
+
const k = p.box.w / 6;
|
|
63
|
+
let d = `M${n(p.box.x - 10)} ${n(Y(p, b))}`;
|
|
64
|
+
for (let i = 0; i < 8; i++) d += `q${n(k / 2)} ${n(i % 2 ? k * 0.35 : -k * 0.35)} ${n(k)} 0`;
|
|
65
|
+
return `<path d="${d}" stroke="${p.color}" stroke-width="${n(p.box.h / 18)}" fill="none" stroke-linecap="round"/>`;
|
|
66
|
+
}).join(''),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'cow', label: 'Vaca', en: 'Cow', pt: 'Vaquinha', as3d: 'spots',
|
|
70
|
+
draw: (p) => [[0.12, 0.35, 0.16, 0.12, 20], [0.85, 0.22, 0.14, 0.1, -15], [0.72, 0.72, 0.18, 0.12, 30], [0.2, 0.85, 0.13, 0.1, -25], [0.5, 0.05, 0.12, 0.08, 10]].map(([a, b, rx, ry, rot]) => {
|
|
71
|
+
const cx = X(p, a);
|
|
72
|
+
const cy = Y(p, b);
|
|
73
|
+
const RX = rx * p.box.w;
|
|
74
|
+
const RY = ry * p.box.h;
|
|
75
|
+
return `<path d="M${n(cx - RX)} ${n(cy)}C${n(cx - RX)} ${n(cy - RY * 1.2)} ${n(cx - RX * 0.1)} ${n(cy - RY * 0.8)} ${n(cx + RX * 0.2)} ${n(cy - RY)}C${n(cx + RX * 1.1)} ${n(cy - RY * 1.1)} ${n(cx + RX * 1.1)} ${n(cy + RY * 0.4)} ${n(cx + RX * 0.5)} ${n(cy + RY * 0.8)}C${n(cx)} ${n(cy + RY * 1.3)} ${n(cx - RX * 1.1)} ${n(cy + RY)} ${n(cx - RX)} ${n(cy)}Z" transform="rotate(${rot} ${n(cx)} ${n(cy)})" fill="${p.color}"/>`;
|
|
76
|
+
}).join(''),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'leopard', label: 'Leopardo', en: 'Leopard', pt: 'Onça', as3d: 'spots',
|
|
80
|
+
draw: (p) => [[0.15, 0.3], [0.45, 0.2], [0.8, 0.32], [0.28, 0.55], [0.62, 0.5], [0.9, 0.65], [0.12, 0.78], [0.45, 0.8], [0.75, 0.9]].map(([a, b], i) => {
|
|
81
|
+
const r = p.box.w * 0.065;
|
|
82
|
+
const cx = X(p, a);
|
|
83
|
+
const cy = Y(p, b);
|
|
84
|
+
const rot = i * 47;
|
|
85
|
+
return `<g transform="rotate(${rot} ${n(cx)} ${n(cy)})"><ellipse cx="${n(cx)}" cy="${n(cy)}" rx="${n(r * 0.9)}" ry="${n(r * 0.7)}" fill="${shade(p.color, 0.25)}"/><path d="M${n(cx - r)} ${n(cy + r * 0.3)}A${n(r)} ${n(r * 0.8)} 0 0 1 ${n(cx + r * 0.6)} ${n(cy - r * 0.75)}M${n(cx + r)} ${n(cy - r * 0.1)}A${n(r)} ${n(r * 0.8)} 0 0 1 ${n(cx - r * 0.2)} ${n(cy + r * 0.85)}" stroke="${shade(p.color, -0.45)}" stroke-width="${n(r * 0.4)}" fill="none" stroke-linecap="round"/></g>`;
|
|
86
|
+
}).join(''),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'socks', label: 'Calcetines', en: 'Socks', pt: 'Meias', as3d: 'twotone',
|
|
90
|
+
draw: (p) => `<path d="${wavyBand(p, Y(p, 0.8), true, p.box.h * 0.035, 5)}" fill="${p.color}"/>`,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'cap', label: 'Gorrito', en: 'Cap', pt: 'Touquinha', as3d: 'twotone',
|
|
94
|
+
draw: (p) => `<path d="${wavyBand(p, Math.min(Y(p, 0.28), p.eyeY - p.r * 1.9), false, p.box.h * 0.035, 4)}" fill="${p.color}"/>`,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'heartbelly', label: 'Barriga corazón', en: 'Heart belly', pt: 'Barriga coração', as3d: 'belly',
|
|
98
|
+
draw: (p) => `<path d="${heartPath(p.box.w * 0.24)}" transform="translate(${n(X(p, 0.5))} ${n(Y(p, 0.74))})" fill="${p.color}"/>`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'starbelly', label: 'Barriga estrella', en: 'Star belly', pt: 'Barriga estrela', as3d: 'belly',
|
|
102
|
+
draw: (p) => `<path d="${star(p.box.w * 0.24, 0.5)}" transform="translate(${n(X(p, 0.5))} ${n(Y(p, 0.76))})" fill="${p.color}" stroke="${p.color}" stroke-width="${n(p.box.w * 0.05)}" stroke-linejoin="round"/>`,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'galaxy', label: 'Galaxia', en: 'Galaxy', pt: 'Galáxia', as3d: 'stars',
|
|
106
|
+
draw: (p) => {
|
|
107
|
+
let out = '';
|
|
108
|
+
let s = 7;
|
|
109
|
+
const rnd = () => ((s = (s * 16807) % 2147483647) / 2147483647);
|
|
110
|
+
for (let i = 0; i < 26; i++) {
|
|
111
|
+
const x = X(p, rnd());
|
|
112
|
+
const y = Y(p, rnd());
|
|
113
|
+
const k = rnd();
|
|
114
|
+
out += k > 0.82 ? `<path d="${star(p.box.w * 0.055, 0.3, 4)}" transform="translate(${n(x)} ${n(y)})" fill="${p.color}"/>` : `<circle cx="${n(x)}" cy="${n(y)}" r="${n(p.box.w * (0.01 + k * 0.016))}" fill="${p.color}" opacity="${n(0.7 + k * 0.3)}"/>`;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
};
|
package/src/character/parts.js
CHANGED
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
import '../i18n/messages/character.js';
|
|
19
19
|
import { t as globalT, addCatalog } from '../i18n/index.js';
|
|
20
|
+
import moreEyes from './more/eyes.js';
|
|
21
|
+
import moreBrows from './more/brows.js';
|
|
22
|
+
import moreMouths from './more/mouths.js';
|
|
23
|
+
import moreCheeks from './more/cheeks.js';
|
|
24
|
+
import morePatterns from './more/patterns.js';
|
|
25
|
+
import moreAccHead from './more/acc-head.js';
|
|
26
|
+
import moreAccRest from './more/acc-rest.js';
|
|
20
27
|
|
|
21
28
|
const n = (v) => Math.round(v * 100) / 100;
|
|
22
29
|
|
|
@@ -125,6 +132,41 @@ export const SHAPES = [
|
|
|
125
132
|
}),
|
|
126
133
|
},
|
|
127
134
|
{ id: 'mushroom', label: 'Seta', points: superShape({ rxT: 78, rxB: 46, ryT: 62, ryB: 64, nT: 2.2, nB: 3.2 }) },
|
|
135
|
+
// ── catálogo 2 (2026-10): añadidas al final; las semillas antiguas no las sacan (ver CATALOG_V1) ──
|
|
136
|
+
{
|
|
137
|
+
id: 'peanut',
|
|
138
|
+
label: 'Cacahuete', en: 'Peanut', pt: 'Amendoim',
|
|
139
|
+
points: superShape({ rxT: 56, rxB: 64, ryT: 72, ryB: 60, nT: 2.2, nB: 2.4, tweak: (i, x, y) => (i === 4 || i === 12 ? [100 + (x - 100) * 0.84, y + 4] : i === 3 || i === 13 ? [100 + (x - 100) * 1.04, y] : [x, y]) }),
|
|
140
|
+
},
|
|
141
|
+
{ id: 'onigiri', label: 'Onigiri', en: 'Onigiri', pt: 'Onigiri', points: superShape({ rxT: 40, rxB: 72, ryT: 74, ryB: 54, nT: 1.5, nB: 3.4 }) },
|
|
142
|
+
{ id: 'bell', label: 'Campana', en: 'Bell', pt: 'Sino', points: superShape({ rxT: 48, rxB: 74, ryT: 72, ryB: 54, nT: 2.2, nB: 5.5 }) },
|
|
143
|
+
{ id: 'bun', label: 'Bollito', en: 'Bun', pt: 'Pãozinho', points: superShape({ rxT: 70, rxB: 74, ryT: 70, ryB: 40, nT: 2.1, nB: 5 }) },
|
|
144
|
+
{
|
|
145
|
+
id: 'bear',
|
|
146
|
+
label: 'Osito', en: 'Teddy', pt: 'Ursinho',
|
|
147
|
+
points: superShape({
|
|
148
|
+
rxT: 60, rxB: 64, ryT: 62, ryB: 60, nT: 2.3, nB: 2.4,
|
|
149
|
+
tweak: (i, x, y) => (i === 2 || i === 14 ? [x + (i === 2 ? 14 : -14), y - 16] : i === 1 || i === 15 ? [x, y + 6] : [x, y]),
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
{ id: 'potato', label: 'Patata', en: 'Potato', pt: 'Batata', points: superShape({ rxT: 62, rxB: 66, ryT: 62, ryB: 58, nT: 2.3, nB: 2.5, tweak: (i, x, y) => [x + Math.sin(i * 2.9 + 1) * 6, y + Math.cos(i * 1.3 + 2) * 6] }) },
|
|
153
|
+
{ id: 'gem', label: 'Gema', en: 'Gem', pt: 'Gema', points: superShape({ rxT: 66, rxB: 66, ryT: 66, ryB: 70, nT: 1.35, nB: 1.35 }) },
|
|
154
|
+
{ id: 'chubby', label: 'Rechoncho', en: 'Chubby', pt: 'Fofinho', points: superShape({ rxT: 58, rxB: 82, ryT: 60, ryB: 46, nT: 2.2, nB: 3.2 }) },
|
|
155
|
+
{ id: 'capsule', label: 'Cápsula', en: 'Capsule', pt: 'Cápsula', points: superShape({ rxT: 48, rxB: 48, ryT: 78, ryB: 72, nT: 2.6, nB: 2.6 }) },
|
|
156
|
+
{ id: 'barrel', label: 'Tonel', en: 'Barrel', pt: 'Barril', points: superShape({ rxT: 64, rxB: 64, ryT: 64, ryB: 62, nT: 3, nB: 3, tweak: (i, x, y) => (i === 4 || i === 12 ? [100 + (x - 100) * 1.06, y] : [x, y]) }) },
|
|
157
|
+
{ id: 'flame', label: 'Llama', en: 'Flame', pt: 'Chama', points: superShape({ rxT: 40, rxB: 64, ryT: 80, ryB: 54, nT: 1.6, nB: 2.4, tweak: (i, x, y) => (i === 0 ? [x + 16, y + 4] : i === 1 ? [x + 6, y] : i === 15 ? [x + 8, y] : [x, y]) }) },
|
|
158
|
+
{
|
|
159
|
+
id: 'owl',
|
|
160
|
+
label: 'Búho', en: 'Owl', pt: 'Coruja',
|
|
161
|
+
points: superShape({
|
|
162
|
+
rxT: 60, rxB: 64, ryT: 62, ryB: 60, nT: 3, nB: 2.4,
|
|
163
|
+
tweak: (i, x, y) => (i === 2 || i === 14 ? [x + (i === 2 ? 4 : -4), y - 20] : i === 1 || i === 15 ? [x, y + 10] : i === 0 ? [x, y + 10] : [x, y]),
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
{ id: 'slime', label: 'Slime', en: 'Slime', pt: 'Slime', points: superShape({ rxT: 56, rxB: 80, ryT: 78, ryB: 30, nT: 2, nB: 6, tweak: (i, x, y) => (i >= 6 && i <= 10 ? [x, y + (i % 2 ? 4 : -1)] : [x, y]) }) },
|
|
167
|
+
{ id: 'apple', label: 'Manzana', en: 'Apple', pt: 'Maçã', points: superShape({ rxT: 64, rxB: 60, ryT: 62, ryB: 62, nT: 2.4, nB: 2.2, tweak: (i, x, y) => (i === 0 ? [x, y + 12] : i === 1 || i === 15 ? [x, y + 2] : [x, y]) }) },
|
|
168
|
+
{ id: 'lemon', label: 'Limón', en: 'Lemon', pt: 'Limão', points: superShape({ rxT: 70, rxB: 70, ryT: 56, ryB: 56, nT: 2, nB: 2, tweak: (i, x, y) => (i === 4 || i === 12 ? [x + (i === 4 ? 12 : -12), y] : [x, y]) }) },
|
|
169
|
+
{ id: 'alien', label: 'Alien', en: 'Alien', pt: 'Alienígena', points: superShape({ rxT: 72, rxB: 44, ryT: 64, ryB: 66, nT: 2.3, nB: 1.7 }) },
|
|
128
170
|
];
|
|
129
171
|
|
|
130
172
|
// ───────────────────────────── ojos ─────────────────────────────
|
|
@@ -236,6 +278,7 @@ export const EYES = [
|
|
|
236
278
|
{ id: 'teary', label: 'Llorosos', draw: (p) => { const [x, y] = look(p, 0.1); return `<circle cx="${x}" cy="${y}" r="${n(p.r * 0.95)}" fill="${p.ink}"/>${shine(x - p.r * 0.32, y - p.r * 0.34, p.r * 0.32)}${shine(x + p.r * 0.3, y + p.r * 0.34, p.r * 0.14)}<path d="M${n(p.r * 0.7 * p.side)} ${n(p.r * 0.9)}q${n(-p.r * 0.35)} ${n(p.r * 0.6)} 0 ${n(p.r * 0.8)}q${n(p.r * 0.35)} ${n(-p.r * 0.2)} 0 ${n(-p.r * 0.8)}Z" fill="#60a5fa"/>`; } },
|
|
237
279
|
{ id: 'hypno', label: 'Hipnosis', draw: (p) => `<circle r="${p.r}" fill="#fff"/>${[0.8, 0.55, 0.3].map((k, i) => `<circle r="${n(p.r * k)}" fill="none" stroke="${i % 2 ? p.iris : p.ink}" stroke-width="${n(p.r * 0.14)}"/>`).join('')}` },
|
|
238
280
|
{ id: 'focused', label: 'Decididos', draw: (p) => `<ellipse rx="${n(p.r)}" ry="${n(p.r * 0.7)}" fill="${p.white}"/>${pupil(p, p.r * 0.5, 0.3)}<path d="M${n(-p.r * 1.1)} ${n(-p.r * 0.55)}H${n(p.r * 1.1)}" stroke="${p.ink}" stroke-width="${n(p.r * 0.24)}" stroke-linecap="round"/>` },
|
|
281
|
+
...moreEyes({ n, shade, shine, star, heartPath, look, pupil }),
|
|
239
282
|
];
|
|
240
283
|
|
|
241
284
|
// ───────────────────────────── cejas ─────────────────────────────
|
|
@@ -252,6 +295,7 @@ export const BROWS = [
|
|
|
252
295
|
{ id: 'dots', label: 'Maro', draw: (p) => `<ellipse rx="${n(p.r * 0.38)}" ry="${n(p.r * 0.24)}" fill="${p.color}"/>` },
|
|
253
296
|
{ id: 'unibrow', label: 'Uniceja', uni: true, draw: (p) => brow(p, `M${n(-p.sp - p.r * 0.9)} ${n(p.r * 0.1)}Q${n(-p.sp / 2)} ${n(-p.r * 0.45)} 0 ${n(p.r * 0.05)}Q${n(p.sp / 2)} ${n(-p.r * 0.45)} ${n(p.sp + p.r * 0.9)} ${n(p.r * 0.1)}`, 0.34) },
|
|
254
297
|
{ id: 'angled', label: 'Picudas', draw: (p) => brow(p, `M${n(-p.r * 0.9 * p.side)} ${n(p.r * 0.15)}L${n(p.r * 0.35 * p.side)} ${n(-p.r * 0.35)}L${n(p.r * 0.9 * p.side)} ${n(-p.r * 0.05)}`, 0.2) },
|
|
298
|
+
...moreBrows({ n, shade, brow, star, heartPath }),
|
|
255
299
|
];
|
|
256
300
|
|
|
257
301
|
// ───────────────────────────── bocas ─────────────────────────────
|
|
@@ -334,6 +378,7 @@ export const MOUTHS = [
|
|
|
334
378
|
{ id: 'drool', label: 'Babeando', draw: (p) => `${openMouth({ ...p, curve: Math.max(0.4, p.curve + 0.6) }, { min: 0.2 })}<path d="M${n(p.w * 0.6)} ${n(p.w * 0.1)}q${n(-p.w * 0.12)} ${n(p.w * 0.5)} 0 ${n(p.w * 0.65)}q${n(p.w * 0.12)} ${n(-p.w * 0.15)} 0 ${n(-p.w * 0.65)}Z" fill="#93c5fd"/>` },
|
|
335
379
|
{ id: 'square', label: 'Cuadrada', draw: (p) => { const h = p.w * (0.3 + p.open * 0.7); return `<rect x="${n(-p.w * 0.7)}" y="${n(-h / 2)}" width="${n(p.w * 1.4)}" height="${n(h)}" rx="2" fill="${p.ink}" stroke="${p.lip}" stroke-width="2"/>`; } },
|
|
336
380
|
{ id: 'mustachio', label: 'Sonrisa ladeada', draw: (p) => openMouth({ ...p, curve: p.curve + 0.3 }, { min: 0.1, max: 0.8, teeth: true }) },
|
|
381
|
+
...moreMouths({ n, shade, mouthShape, openMouth, line, closedOr, star, heartPath }),
|
|
337
382
|
];
|
|
338
383
|
|
|
339
384
|
// ───────────────────────────── mejillas y marcas ─────────────────────────────
|
|
@@ -348,6 +393,7 @@ export const CHEEKS = [
|
|
|
348
393
|
{ id: 'whiskers', label: 'Bigotes de gato', draw: (p) => [-1, 1].map((s) => [-0.25, 0, 0.25].map((k) => `<path d="M${n(s * (p.sp * 0.7))} ${n(p.r * 2.4 + k * p.r * 2)}L${n(s * (p.sp + p.r * 2.6))} ${n(p.r * 2.2 + k * p.r * 3.2)}" stroke="#1f2937" stroke-width="${n(p.r * 0.1)}" stroke-linecap="round" opacity=".7"/>`).join('')).join('') },
|
|
349
394
|
{ id: 'stars', label: 'Estrellitas', draw: (p) => [-1, 1].map((s) => `<path d="${star(p.r * 0.4)}" transform="translate(${n(s * (p.sp + p.r * 0.6))} ${n(p.r * 1.9)})" fill="#facc15"/>`).join('') },
|
|
350
395
|
{ id: 'paint', label: 'Pintura de guerra', draw: (p) => [-1, 1].map((s) => `<path d="M${n(s * (p.sp - p.r * 0.2))} ${n(p.r * 1.4)}h${n(s * p.r * 1.6)}M${n(s * (p.sp - p.r * 0.2))} ${n(p.r * 1.9)}h${n(s * p.r * 1.6)}" stroke="${p.color}" stroke-width="${n(p.r * 0.25)}" stroke-linecap="round"/>`).join('') },
|
|
396
|
+
...moreCheeks({ n, shade, star, heartPath }),
|
|
351
397
|
];
|
|
352
398
|
|
|
353
399
|
// ───────────────────────────── patrones del cuerpo ─────────────────────────────
|
|
@@ -364,6 +410,7 @@ export const PATTERNS = [
|
|
|
364
410
|
{ id: 'hearts', label: 'Corazones', draw: (p) => [[0.2, 0.35], [0.8, 0.3], [0.7, 0.75], [0.25, 0.8], [0.5, 0.95]].map(([a, b]) => `<path d="${heartPath(p.box.w * 0.06)}" transform="translate(${n(p.box.x + a * p.box.w)} ${n(p.box.y + b * p.box.h)})" fill="${p.color}"/>`).join('') },
|
|
365
411
|
{ id: 'stars', label: 'Estrellas', draw: (p) => [[0.18, 0.3], [0.82, 0.28], [0.75, 0.72], [0.22, 0.78], [0.5, 0.93]].map(([a, b]) => `<path d="${star(p.box.w * 0.06)}" transform="translate(${n(p.box.x + a * p.box.w)} ${n(p.box.y + b * p.box.h)})" fill="${p.color}"/>`).join('') },
|
|
366
412
|
{ id: 'circuit', label: 'Circuito', draw: (p) => [0.25, 0.5, 0.75].map((b, i) => `<path d="M${n(p.box.x)} ${n(p.box.y + b * p.box.h)}h${n(p.box.w * 0.18)}l${n(p.box.w * 0.06)} ${n(i % 2 ? 8 : -8)}h${n(p.box.w * 0.1)}M${n(p.box.x + p.box.w)} ${n(p.box.y + b * p.box.h + 6)}h${n(-p.box.w * 0.2)}" stroke="${p.color}" stroke-width="2" fill="none"/><circle cx="${n(p.box.x + p.box.w * 0.34)}" cy="${n(p.box.y + b * p.box.h + (i % 2 ? 8 : -8))}" r="2.5" fill="${p.color}"/>`).join('') },
|
|
413
|
+
...morePatterns({ n, shade, star, heartPath }),
|
|
367
414
|
];
|
|
368
415
|
|
|
369
416
|
// ───────────────────────────── acabados ─────────────────────────────
|
|
@@ -376,6 +423,14 @@ export const FINISHES = [
|
|
|
376
423
|
{ id: 'toon', label: 'Cómic' },
|
|
377
424
|
{ id: 'neon', label: 'Neón' },
|
|
378
425
|
{ id: 'sketch', label: 'Boceto' },
|
|
426
|
+
// catálogo 2 (2026-10): se dibujan en Character.js (FINISH2)
|
|
427
|
+
{ id: 'pastel', label: 'Pastel', en: 'Pastel', pt: 'Pastel', as3d: 'soft3d' },
|
|
428
|
+
{ id: 'metal', label: 'Metálico', en: 'Metallic', pt: 'Metálico', as3d: 'glossy' },
|
|
429
|
+
{ id: 'pearl', label: 'Nácar', en: 'Pearl', pt: 'Madrepérola', as3d: 'glossy' },
|
|
430
|
+
{ id: 'glass', label: 'Cristal', en: 'Glass', pt: 'Vidro', as3d: 'glossy' },
|
|
431
|
+
{ id: 'velvet', label: 'Terciopelo', en: 'Velvet', pt: 'Veludo', as3d: 'clay' },
|
|
432
|
+
{ id: 'holo', label: 'Holográfico', en: 'Holographic', pt: 'Holográfico', as3d: 'glossy' },
|
|
433
|
+
{ id: 'vinyl', label: 'Vinilo', en: 'Vinyl toy', pt: 'Vinil', as3d: 'toon' },
|
|
379
434
|
];
|
|
380
435
|
|
|
381
436
|
// ───────────────────────────── texturas ─────────────────────────────
|
|
@@ -392,6 +447,16 @@ export const TEXTURES = [
|
|
|
392
447
|
{ id: 'ridges', label: 'Anillos' },
|
|
393
448
|
{ id: 'gloss', label: 'Gelatina' },
|
|
394
449
|
{ id: 'panels', label: 'Paneles' },
|
|
450
|
+
// catálogo 2 (2026-10): solo se eligen a mano ('auto' nunca las saca)
|
|
451
|
+
{ id: 'knit', label: 'Punto', en: 'Knit', pt: 'Tricô', as3d: 'fuzz' },
|
|
452
|
+
{ id: 'polka', label: 'Lunares', en: 'Polka dots', pt: 'Bolinhas', as3d: 'speckles' },
|
|
453
|
+
{ id: 'glitter', label: 'Purpurina', en: 'Glitter', pt: 'Purpurina', as3d: 'speckles' },
|
|
454
|
+
{ id: 'stone', label: 'Piedra', en: 'Stone', pt: 'Pedra', as3d: 'speckles' },
|
|
455
|
+
{ id: 'wood', label: 'Madera', en: 'Wood', pt: 'Madeira', as3d: 'ridges' },
|
|
456
|
+
{ id: 'bubbles', label: 'Burbujas', en: 'Bubbles', pt: 'Bolhas', as3d: 'gloss' },
|
|
457
|
+
{ id: 'stitches', label: 'Costuras', en: 'Stitches', pt: 'Costuras', as3d: 'fuzz' },
|
|
458
|
+
{ id: 'honeycomb', label: 'Panal', en: 'Honeycomb', pt: 'Favo de mel', as3d: 'scales' },
|
|
459
|
+
{ id: 'quilt', label: 'Acolchado', en: 'Quilted', pt: 'Acolchoado', as3d: 'fuzz' },
|
|
395
460
|
];
|
|
396
461
|
|
|
397
462
|
addCatalog({
|
|
@@ -561,6 +626,8 @@ export const ACCESSORIES = [
|
|
|
561
626
|
{ id: 'hearts', label: 'Corazones', group: 'extra', anchor: 'top', color: '#f43f5e', draw: () => [[-40, -10, 7], [36, -18, 9], [8, -34, 6]].map(([x, y, r]) => `<path d="${heartPath(r)}" transform="translate(${x} ${y})" fill="#f43f5e"/>`).join('') },
|
|
562
627
|
{ id: 'zzz', label: 'Zzz', group: 'extra', anchor: 'top', color: '#60a5fa', draw: (c) => `<text x="30" y="-10" font-family="Arial Black,Arial,sans-serif" font-weight="900" font-size="18" fill="${c.color}">Z</text><text x="46" y="-26" font-family="Arial Black,Arial,sans-serif" font-weight="900" font-size="13" fill="${c.color}">z</text><text x="58" y="-38" font-family="Arial Black,Arial,sans-serif" font-weight="900" font-size="10" fill="${c.color}">z</text>` },
|
|
563
628
|
{ id: 'sweat', label: 'Gota de sudor', group: 'extra', anchor: 'eyes', color: '#60a5fa', draw: (c) => `<path d="M${n(c.sp + c.r * 2.4)} ${n(-c.r * 2.4)}q-7 10 0 14q7 -4 0 -14Z" fill="${c.color}" stroke="#1e40af" stroke-width="1.5"/>` },
|
|
629
|
+
...moreAccHead({ n, shade, S, star, heartPath, ellD, hornD, catEarD, tailD, ANGEL_WING, BAT_WING }),
|
|
630
|
+
...moreAccRest({ n, shade, S, star, heartPath, ellD, hornD, catEarD, tailD, ANGEL_WING, BAT_WING }),
|
|
564
631
|
];
|
|
565
632
|
|
|
566
633
|
// ───────────────────────────── estilos (plantillas completas) ─────────────────────────────
|
|
@@ -596,5 +663,28 @@ export const PRESETS = [
|
|
|
596
663
|
{ id: 'sprout', label: 'Brotecito', spec: { body: { shape: 'pear', color: '#86efac' }, finish: 'glossy', eyes: { type: 'kawaii' }, mouth: { type: 'tiny' }, cheeks: { type: 'blush' }, accessories: [acc('sprout')] } },
|
|
597
664
|
];
|
|
598
665
|
|
|
666
|
+
/**
|
|
667
|
+
* Tamaño de cada lista en el catálogo 1 (antes de 2026-10). randomCharacter(seed, { legacy: true })
|
|
668
|
+
* sortea solo entre estas primeras piezas para que las semillas antiguas sigan dando el mismo ot.
|
|
669
|
+
* Las piezas nuevas van SIEMPRE al final de su lista.
|
|
670
|
+
*/
|
|
671
|
+
export const CATALOG_V1 = { shapes: 16, eyes: 33, brows: 9, mouths: 28, cheeks: 8, patterns: 10, finishes: 7, textures: 10, accessories: 80 };
|
|
672
|
+
|
|
599
673
|
/** Listas por tipo (para partLabel). Moods y gestos viven en Character.js y solo tienen catálogo. */
|
|
600
674
|
const CATALOG = { shapes: SHAPES, eyes: EYES, brows: BROWS, mouths: MOUTHS, cheeks: CHEEKS, patterns: PATTERNS, finishes: FINISHES, textures: TEXTURES, accessories: ACCESSORIES, groups: ACCESSORY_GROUPS, presets: PRESETS };
|
|
675
|
+
|
|
676
|
+
// Textos en/pt de las piezas del catálogo 2 (llevan `en` y `pt` junto al `label` en español).
|
|
677
|
+
{
|
|
678
|
+
const by = { es: {}, en: {}, pt: {} };
|
|
679
|
+
for (const [kind, list] of Object.entries(CATALOG)) {
|
|
680
|
+
for (const x of list) {
|
|
681
|
+
if (Array.isArray(x) || !x.en) continue;
|
|
682
|
+
for (const l of ['es', 'en', 'pt']) (by[l][kind] ||= {})[x.id] = l === 'es' ? x.label : x[l];
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
addCatalog({ es: { character: by.es }, en: { character: by.en }, pt: { character: by.pt } });
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const ALIAS3D = Object.fromEntries(['eyes', 'brows', 'mouths', 'cheeks', 'patterns', 'accessories', 'finishes', 'textures'].map((k) => [k, Object.fromEntries(CATALOG[k].filter((x) => x.as3d).map((x) => [x.id, x.as3d]))]));
|
|
689
|
+
/** Id equivalente para el visor 3D (las piezas nuevas sin modelo propio usan la más parecida). */
|
|
690
|
+
export const as3d = (kind, id) => ALIAS3D[kind]?.[id] || id;
|
package/src/character/skin.js
CHANGED
|
@@ -55,6 +55,23 @@ const AUTO = {
|
|
|
55
55
|
wide: ['scales', 'ridges', 'fuzz', 'speckles'],
|
|
56
56
|
tall: ['ridges', 'scales', 'fur', 'speckles'],
|
|
57
57
|
squircle: ['scales', 'fuzz', 'speckles', 'panels'],
|
|
58
|
+
// catálogo 2 (formas nuevas; las antiguas no cambian)
|
|
59
|
+
bear: ['fur', 'fur', 'fuzz'],
|
|
60
|
+
owl: ['feathers', 'feathers', 'fuzz'],
|
|
61
|
+
slime: ['gloss', 'gloss', 'speckles'],
|
|
62
|
+
gem: ['gloss', 'panels'],
|
|
63
|
+
barrel: ['ridges', 'panels'],
|
|
64
|
+
capsule: ['gloss', 'panels', 'none'],
|
|
65
|
+
potato: ['speckles', 'speckles', 'fuzz'],
|
|
66
|
+
apple: ['gloss', 'speckles'],
|
|
67
|
+
lemon: ['speckles', 'gloss'],
|
|
68
|
+
peanut: ['speckles', 'ridges'],
|
|
69
|
+
bun: ['fuzz', 'speckles', 'none'],
|
|
70
|
+
onigiri: ['none', 'speckles'],
|
|
71
|
+
bell: ['gloss', 'none'],
|
|
72
|
+
flame: ['gloss', 'fuzz'],
|
|
73
|
+
alien: ['gloss', 'scales', 'speckles'],
|
|
74
|
+
chubby: ['fur', 'fuzz', 'speckles'],
|
|
58
75
|
};
|
|
59
76
|
const AUTO_ANY = ['fur', 'fuzz', 'scales', 'feathers', 'speckles', 'ridges', 'gloss', 'fuzz', 'fur', 'none'];
|
|
60
77
|
|
|
@@ -65,6 +82,7 @@ export function textureOf(spec) {
|
|
|
65
82
|
const f = spec.finish;
|
|
66
83
|
const sh = spec.body.shape;
|
|
67
84
|
if (f === 'neon' || f === 'sketch') return 'none';
|
|
85
|
+
if (f === 'metal' || f === 'glass' || f === 'holo' || f === 'pearl') return 'none'; // acabados lisos del catálogo 2
|
|
68
86
|
if (sh === 'robot' || spec.body.pattern === 'circuit') return 'panels';
|
|
69
87
|
if (sh === 'cat' || spec.body.pattern === 'tiger' || spec.accessories.some((a) => a.id === 'catears' || a.id === 'bunny')) return 'fur';
|
|
70
88
|
const h = skinHash(spec);
|
|
@@ -208,7 +226,154 @@ function speckTile(u, r) {
|
|
|
208
226
|
return { W, H: W, body: blot(dk, '#000', 0.16) };
|
|
209
227
|
}
|
|
210
228
|
|
|
211
|
-
|
|
229
|
+
/** Punto de media: filas de uves. */
|
|
230
|
+
function knitTile(u) {
|
|
231
|
+
const W = 7 * u;
|
|
232
|
+
const H = 6 * u;
|
|
233
|
+
let dk = '';
|
|
234
|
+
let lt = '';
|
|
235
|
+
for (const y of [-H, 0, H]) {
|
|
236
|
+
dk += `M0 ${n(y)}L${n(W / 2)} ${n(y + H * 0.9)}L${n(W)} ${n(y)}`;
|
|
237
|
+
lt += `M${n(0.9 * u)} ${n(y + 0.6 * u)}L${n(W / 2)} ${n(y + H * 0.6)}L${n(W - 0.9 * u)} ${n(y + 0.6 * u)}`;
|
|
238
|
+
}
|
|
239
|
+
return { W, H, body: ink(dk, '#000', 0.16, 1.1 * u) + ink(lt, '#fff', 0.22, 0.8 * u) };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Lunares regulares. */
|
|
243
|
+
function polkaTile(u) {
|
|
244
|
+
const W = 14 * u;
|
|
245
|
+
const rr = 2.3 * u;
|
|
246
|
+
let lt = '';
|
|
247
|
+
for (const [x, y] of [[W / 4, W / 4], [(3 * W) / 4, (3 * W) / 4]]) lt += dot(x, y, rr);
|
|
248
|
+
return { W, H: W, body: blot(lt, '#fff', 0.5) };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Purpurina: destellos de cuatro puntas y motas. */
|
|
252
|
+
function glitterTile(u, r) {
|
|
253
|
+
const W = 12 * u;
|
|
254
|
+
const star = (x, y, s) =>
|
|
255
|
+
`M${n(x)} ${n(y - s)}L${n(x + s * 0.22)} ${n(y - s * 0.22)}L${n(x + s)} ${n(y)}L${n(x + s * 0.22)} ${n(y + s * 0.22)}L${n(x)} ${n(y + s)}L${n(x - s * 0.22)} ${n(y + s * 0.22)}L${n(x - s)} ${n(y)}L${n(x - s * 0.22)} ${n(y - s * 0.22)}Z`;
|
|
256
|
+
let lt = '';
|
|
257
|
+
let dk = '';
|
|
258
|
+
for (let i = 0; i < 9; i++) {
|
|
259
|
+
const x = r() * W;
|
|
260
|
+
const y = r() * W;
|
|
261
|
+
const s = (i < 2 ? 1.8 : 0.4 + r() * 0.5) * u;
|
|
262
|
+
for (const [ox, oy] of wrap([x - s, y - s, x + s, y + s], W, W)) {
|
|
263
|
+
if (i < 2) lt += star(x + ox, y + oy, s);
|
|
264
|
+
else if (i % 3) lt += dot(x + ox, y + oy, s);
|
|
265
|
+
else dk += dot(x + ox, y + oy, s);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { W, H: W, body: blot(dk, '#000', 0.18) + blot(lt, '#fff', 0.75) };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Piedra: grietas finas y granitos. */
|
|
272
|
+
function stoneTile(u, r) {
|
|
273
|
+
const W = 26 * u;
|
|
274
|
+
const cl = (v) => Math.min(W, Math.max(0, v));
|
|
275
|
+
let ck = '';
|
|
276
|
+
let dk = '';
|
|
277
|
+
let lt = '';
|
|
278
|
+
for (let i = 0; i < 3; i++) {
|
|
279
|
+
let x = r() * W;
|
|
280
|
+
let y = r() * W;
|
|
281
|
+
ck += `M${n(x)} ${n(y)}`;
|
|
282
|
+
const a = r() * Math.PI * 2;
|
|
283
|
+
for (let k = 0; k < 3; k++) {
|
|
284
|
+
const b = a + (r() - 0.5) * 1.4;
|
|
285
|
+
x = cl(x + Math.cos(b) * 4.2 * u);
|
|
286
|
+
y = cl(y + Math.sin(b) * 4.2 * u);
|
|
287
|
+
ck += `L${n(x)} ${n(y)}`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (let i = 0; i < 10; i++) {
|
|
291
|
+
const x = r() * W;
|
|
292
|
+
const y = r() * W;
|
|
293
|
+
const rr = (0.3 + r() * 0.5) * u;
|
|
294
|
+
for (const [ox, oy] of wrap([x - rr, y - rr, x + rr, y + rr], W, W)) {
|
|
295
|
+
if (i % 2) dk += dot(x + ox, y + oy, rr);
|
|
296
|
+
else lt += dot(x + ox, y + oy, rr);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const lip = `<path d="${ck}" fill="none" stroke="#fff" stroke-opacity=".22" stroke-width="${n(0.6 * u)}" stroke-linecap="round" stroke-linejoin="round" transform="translate(${n(0.6 * u)} ${n(0.6 * u)})"/>`;
|
|
300
|
+
return { W, H: W, body: lip + ink(ck, '#000', 0.3, 0.8 * u) + blot(dk, '#000', 0.18) + blot(lt, '#fff', 0.3) };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Veta de madera: líneas onduladas verticales. */
|
|
304
|
+
function woodTile(u) {
|
|
305
|
+
const W = 10 * u;
|
|
306
|
+
const H = 40 * u;
|
|
307
|
+
const vein = (x, a) => `M${n(x)} 0Q${n(x + a)} ${n(H / 4)} ${n(x)} ${n(H / 2)}Q${n(x - a)} ${n((3 * H) / 4)} ${n(x)} ${n(H)}`;
|
|
308
|
+
return { W, H, body: ink(vein(W * 0.25, 1.8 * u) + vein(W * 0.7, -1.2 * u), '#000', 0.15, 0.9 * u) + ink(vein(W * 0.45, 1.4 * u), '#fff', 0.16, 0.7 * u) };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Burbujas: aros claros con un brillito. */
|
|
312
|
+
function bubblesTile(u, r) {
|
|
313
|
+
const W = 26 * u;
|
|
314
|
+
let ring = '';
|
|
315
|
+
let shine = '';
|
|
316
|
+
// una por cuadrante, desplazada al azar: repartidas sin amontonarse
|
|
317
|
+
for (let i = 0; i < 4; i++) {
|
|
318
|
+
const rr = (1.3 + r() * 2.2) * u;
|
|
319
|
+
const x = ((i % 2) + 0.2 + r() * 0.6) * (W / 2);
|
|
320
|
+
const y = ((i >> 1) + 0.2 + r() * 0.6) * (W / 2);
|
|
321
|
+
for (const [ox, oy] of wrap([x - rr, y - rr, x + rr, y + rr], W, W)) {
|
|
322
|
+
ring += dot(x + ox, y + oy, rr);
|
|
323
|
+
shine += dot(x + ox - rr * 0.38, y + oy - rr * 0.38, rr * 0.28);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { W, H: W, body: `<path d="${ring}" fill="#fff" fill-opacity=".12" stroke="#fff" stroke-opacity=".5" stroke-width="${n(0.6 * u)}"/>${blot(shine, '#fff', 0.7)}` };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Costuras: retales unidos con pespunte. */
|
|
330
|
+
function stitchTile(u) {
|
|
331
|
+
const W = 22 * u;
|
|
332
|
+
const seam = `M0 ${n(W / 2)}H${n(W)}M${n(W / 2)} 0V${n(W)}`;
|
|
333
|
+
const st = `M0 ${n(W / 2 + 1.5 * u)}H${n(W)}M${n(W / 2 + 1.5 * u)} 0V${n(W)}`;
|
|
334
|
+
const dash = `<path d="${st}" fill="none" stroke="#fff" stroke-opacity=".6" stroke-width="${n(0.7 * u)}" stroke-dasharray="${n(1.6 * u)} ${n(1.4 * u)}"/>`;
|
|
335
|
+
return { W, H: W, body: ink(seam, '#000', 0.2, 0.9 * u) + dash };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Panal: rejilla hexagonal. */
|
|
339
|
+
function honeycombTile(u) {
|
|
340
|
+
const s = 4.6 * u;
|
|
341
|
+
const W = Math.sqrt(3) * s;
|
|
342
|
+
const H = 3 * s;
|
|
343
|
+
const hex = (x, y) => `M${n(x)} ${n(y - s)}L${n(x + W / 2)} ${n(y - s / 2)}L${n(x + W / 2)} ${n(y + s / 2)}L${n(x)} ${n(y + s)}L${n(x - W / 2)} ${n(y + s / 2)}L${n(x - W / 2)} ${n(y - s / 2)}Z`;
|
|
344
|
+
let dk = '';
|
|
345
|
+
for (const [x, y] of [[0, 0], [W, 0], [W / 2, H / 2], [0, H], [W, H]]) dk += hex(x, y);
|
|
346
|
+
const edge = (c, op, w, t) => `<path d="${dk}" fill="none" stroke="${c}" stroke-opacity="${op}" stroke-width="${n(w)}" stroke-linejoin="round"${t ? ` transform="translate(0 ${n(t)})"` : ''}/>`;
|
|
347
|
+
return { W, H, body: edge('#fff', 0.2, 0.7 * u, 0.8 * u) + edge('#000', 0.2, 0.9 * u, 0) };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Acolchado: rombos con botones. */
|
|
351
|
+
function quiltTile(u) {
|
|
352
|
+
const W = 16 * u;
|
|
353
|
+
const o = 1.1 * u;
|
|
354
|
+
const dk = `M0 0L${n(W)} ${n(W)}M${n(W)} 0L0 ${n(W)}`;
|
|
355
|
+
const lt = `M0 ${n(o)}L${n(W - o)} ${n(W)}M${n(W)} ${n(o)}L${n(o)} ${n(W)}`;
|
|
356
|
+
let bt = '';
|
|
357
|
+
for (const [x, y] of [[0, 0], [W, 0], [0, W], [W, W], [W / 2, W / 2]]) bt += dot(x, y, 1 * u);
|
|
358
|
+
return { W, H: W, body: ink(lt, '#fff', 0.2, 1 * u) + ink(dk, '#000', 0.15, 1.1 * u) + blot(bt, '#000', 0.22) };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const TILES = {
|
|
362
|
+
fur: furTile,
|
|
363
|
+
fuzz: fuzzTile,
|
|
364
|
+
scales: scalesTile,
|
|
365
|
+
feathers: feathersTile,
|
|
366
|
+
speckles: speckTile,
|
|
367
|
+
knit: knitTile,
|
|
368
|
+
polka: polkaTile,
|
|
369
|
+
glitter: glitterTile,
|
|
370
|
+
stone: stoneTile,
|
|
371
|
+
wood: woodTile,
|
|
372
|
+
bubbles: bubblesTile,
|
|
373
|
+
stitches: stitchTile,
|
|
374
|
+
honeycomb: honeycombTile,
|
|
375
|
+
quilt: quiltTile,
|
|
376
|
+
};
|
|
212
377
|
|
|
213
378
|
// ───────────────────────────── piel del cuerpo ─────────────────────────────
|
|
214
379
|
|