@7ots/cli 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/cli/7ots.mjs +28 -4
  3. package/cli/lib/account.mjs +152 -2
  4. package/cli/lib/assistant.mjs +106 -41
  5. package/cli/lib/brain.mjs +13 -2
  6. package/cli/lib/config.mjs +3 -0
  7. package/cli/lib/inferences.mjs +30 -6
  8. package/cli/lib/integrations.mjs +69 -5
  9. package/cli/lib/orquesta.mjs +44 -1
  10. package/cli/lib/pet-server.mjs +344 -11
  11. package/cli/pet/electron/director.cjs +37 -5
  12. package/cli/pet/electron/main.cjs +92 -10
  13. package/cli/pet/pet.html +149 -10
  14. package/cli/pet/settings.html +545 -42
  15. package/dist/7ots.esm.js +216 -137
  16. package/dist/7ots.esm.js.map +4 -4
  17. package/dist/7ots.iife.js +216 -137
  18. package/dist/7ots.iife.js.map +4 -4
  19. package/package.json +1 -1
  20. package/server/channels/agent.mjs +7 -1
  21. package/server/channels/index.mjs +3 -2
  22. package/server/platform/connect.mjs +4 -1
  23. package/server/platform/db.mjs +9 -1
  24. package/server/platform/device.mjs +101 -4
  25. package/server/platform/mods.mjs +222 -0
  26. package/server/platform/orquesta.mjs +78 -14
  27. package/server/platform/profile.mjs +161 -0
  28. package/server/platform/routes.mjs +38 -33
  29. package/server/platform/runtime.mjs +3 -1
  30. package/server/platform/store.mjs +1 -1
  31. package/server/platform/vm.mjs +370 -91
  32. package/server/server.mjs +12 -2
  33. package/src/avatar/three.js +3 -2
  34. package/src/character/AvatarEditor.js +95 -7
  35. package/src/character/Character.js +116 -17
  36. package/src/character/TradingCard.js +226 -0
  37. package/src/character/mods/index.js +4 -0
  38. package/src/character/mods/orc.js +64 -0
  39. package/src/character/mods.js +251 -0
  40. package/src/character/more/acc-head.js +213 -0
  41. package/src/character/more/acc-rest.js +342 -0
  42. package/src/character/more/brows.js +39 -0
  43. package/src/character/more/cheeks.js +78 -0
  44. package/src/character/more/eyes.js +288 -0
  45. package/src/character/more/mouths.js +259 -0
  46. package/src/character/more/patterns.js +120 -0
  47. package/src/character/parts.js +90 -0
  48. package/src/character/skin.js +166 -1
  49. package/src/character/styles.js +1285 -9
  50. package/src/character3d/Character3D.js +148 -35
  51. package/src/character3d/index.js +7 -1
  52. package/src/character3d/model.js +367 -0
  53. package/src/i18n/messages/character.js +34 -22
  54. package/src/i18n/messages/platform.js +15 -0
  55. package/src/identity/random.js +17 -3
  56. package/src/identity/schema.js +10 -0
  57. package/src/index.js +1 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Carta coleccionable de un ot: la misma carta holográfica del pet de escritorio (cli/pet/card.html),
3
+ * como pieza reutilizable para la web (tipo de avatar «Carta» del editor, dashboard).
4
+ *
5
+ * const card = createTradingCard(div, { identity, id: 'ots_abc', createdAt, foil: 'holo', locale: 'es' });
6
+ * card.update({ identity }); card.setFoil('gold'); card.destroy();
7
+ *
8
+ * Marco metálico en los colores del ot, arte con el personaje (o la imagen propia si la hay), rasgos y
9
+ * rareza sembrados por id + nombre (el mismo ot siempre saca la misma carta) y lámina holo / prisma /
10
+ * cosmos / oro que sigue al puntero; sola se mece como si la giraras en la mano.
11
+ */
12
+ import { createCharacter } from './Character.js';
13
+
14
+ export const CARD_FOILS = ['holo', 'prism', 'cosmos', 'gold', 'none'];
15
+
16
+ const T = {
17
+ en: { stats: ['Energy', 'Wit', 'Charm', 'Chaos'], rar: ['Common', 'Rare', 'Epic', 'Legendary'] },
18
+ es: { stats: ['Energía', 'Ingenio', 'Encanto', 'Caos'], rar: ['Común', 'Rara', 'Épica', 'Legendaria'] },
19
+ pt: { stats: ['Energia', 'Engenho', 'Charme', 'Caos'], rar: ['Comum', 'Rara', 'Épica', 'Lendária'] },
20
+ };
21
+
22
+ const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
23
+ const hex = (v, d) => (/^#[0-9a-f]{6}$/i.test(v || '') ? v : d);
24
+ const STAR = '<svg viewBox="0 0 24 24"><path d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.7 7-6.3-3.9-6.3 3.9 1.7-7L2 9.5l7.1-.6z"/></svg>';
25
+
26
+ /** Rasgos y rareza sembrados: el mismo ot siempre saca la misma carta. */
27
+ export function cardTraits(id, name, locale = 'en') {
28
+ const L = T[String(locale).slice(0, 2)] || T.en;
29
+ let h = 2166136261;
30
+ for (const ch of `${id || name || 'ot'}|${name || ''}`) h = Math.imul(h ^ ch.charCodeAt(0), 16777619);
31
+ const rnd = () => ((h = Math.imul(h ^ (h >>> 15), 2246822507) ^ Math.imul(h ^ (h >>> 13), 3266489909)), (h >>> 0) / 4294967296);
32
+ const stats = L.stats.map((n) => [n, 25 + Math.round(rnd() * 74)]);
33
+ const r = rnd();
34
+ const rarity = r > 0.94 ? 3 : r > 0.75 ? 2 : r > 0.4 ? 1 : 0;
35
+ return { stats, rarity, rarityName: L.rar[rarity], defaultFoil: ['holo', 'holo', 'prism', 'cosmos'][rarity] };
36
+ }
37
+
38
+ const CSS = `
39
+ :host{display:block;--rx:0deg;--ry:0deg;--mx:50%;--my:50%;--o:.6}
40
+ *{box-sizing:border-box}
41
+ .scene{perspective:1400px;display:grid;place-items:center}
42
+ .card{width:100%;max-width:var(--w,360px);aspect-ratio:5/7;position:relative;border-radius:20px;transform-style:preserve-3d;
43
+ transform:rotateY(var(--ry)) rotateX(var(--rx));transition:transform .5s cubic-bezier(.2,.8,.2,1);container-type:inline-size;
44
+ box-shadow:0 30px 60px -20px rgb(0 0 0/.65),0 0 0 1px rgb(255 255 255/.06),0 0 60px -10px color-mix(in srgb,var(--c) 60%,transparent)}
45
+ .card.live{transition:transform .08s linear}
46
+ .frame{position:absolute;inset:0;border-radius:inherit;padding:3.2cqw;overflow:hidden;
47
+ background:linear-gradient(135deg,color-mix(in srgb,var(--c) 70%,#fff) 0%,var(--c) 22%,color-mix(in srgb,var(--a) 80%,#000) 50%,var(--c) 78%,color-mix(in srgb,var(--a) 60%,#fff) 100%)}
48
+ .card[data-foil=gold] .frame{background:linear-gradient(135deg,#fff3b0,#d4a017 20%,#8a6100 45%,#f7d774 62%,#b8860b 80%,#fff1a8)}
49
+ .card[data-foil=cosmos] .frame{background:linear-gradient(135deg,#2b1b5e,#120a2e 40%,#3b2380 70%,#0b0720)}
50
+ .face{position:relative;height:100%;border-radius:3cqw;display:flex;flex-direction:column;overflow:hidden;
51
+ background:linear-gradient(180deg,color-mix(in srgb,var(--c) 22%,#15121e),#0f0c17 60%);color:#f4f1ff;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
52
+ header{display:flex;align-items:baseline;gap:2cqw;padding:2.6cqw 3.6cqw 2cqw}
53
+ .name{font:800 6.6cqw/1 ui-rounded,"SF Pro Rounded",system-ui,sans-serif;letter-spacing:-.01em;text-shadow:0 2px 0 rgb(0 0 0/.35);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
54
+ .lvl{margin-left:auto;font:700 3cqw/1 ui-monospace,monospace;opacity:.9;white-space:nowrap}
55
+ .lvl b{font-size:5cqw}
56
+ .art{position:relative;margin:0 3cqw;flex:1 1 auto;min-height:0;border-radius:2cqw;overflow:hidden;isolation:isolate;
57
+ box-shadow:inset 0 0 0 2px rgb(255 255 255/.25),0 6px 14px rgb(0 0 0/.45);
58
+ background:radial-gradient(120% 90% at 50% 70%,color-mix(in srgb,var(--a) 55%,#fff) 0%,var(--c) 45%,color-mix(in srgb,var(--c) 40%,#000) 100%)}
59
+ .rays{position:absolute;inset:-50%;background:repeating-conic-gradient(from 0deg at 50% 50%,rgb(255 255 255/.14) 0 6deg,transparent 6deg 18deg);animation:spin 60s linear infinite}
60
+ @keyframes spin{to{transform:rotate(1turn)}}
61
+ .dots{position:absolute;inset:0;background-image:radial-gradient(rgb(255 255 255/.35) 1px,transparent 1.5px);background-size:14px 14px;mask-image:linear-gradient(transparent,#000 70%);opacity:.5}
62
+ .floor{position:absolute;left:18%;right:18%;bottom:7%;height:9%;border-radius:50%;background:radial-gradient(rgb(0 0 0/.45),transparent 70%)}
63
+ .fig{position:absolute;left:10%;right:10%;top:6%;bottom:4%;z-index:1}
64
+ .fig>img{width:100%;height:100%;object-fit:contain}
65
+ .type{display:flex;justify-content:space-between;gap:2cqw;margin:2cqw 3cqw 0;padding:1.3cqw 2.6cqw;border-radius:999px;background:rgb(255 255 255/.08);
66
+ font:600 2.8cqw/1.3 system-ui,sans-serif;letter-spacing:.06em;text-transform:uppercase;color:#e6e1ff}
67
+ .type span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
68
+ .type span:last-child{opacity:.7}
69
+ .stats{margin:2.6cqw 3.6cqw 0;display:grid;gap:1.8cqw}
70
+ .stat{display:grid;grid-template-columns:19cqw 1fr 8cqw;align-items:center;gap:2cqw;font:600 3cqw/1 system-ui,sans-serif}
71
+ .bar{height:1.8cqw;border-radius:9px;background:rgb(255 255 255/.1);overflow:hidden}
72
+ .bar i{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--c),var(--a));box-shadow:0 0 10px var(--a)}
73
+ .stat em{font-style:normal;text-align:right;font-family:ui-monospace,monospace}
74
+ .flavor{margin:2.6cqw 3.6cqw 0;padding-top:2cqw;font:italic 3cqw/1.35 Georgia,serif;color:#cfc8ea;border-top:1px solid rgb(255 255 255/.12);
75
+ display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
76
+ footer{margin-top:auto;display:flex;align-items:center;gap:1.5cqw;padding:2cqw 3.6cqw 2.6cqw;font:600 2.5cqw/1 ui-monospace,monospace;color:#b9b1d8;letter-spacing:.04em}
77
+ .rar{margin-left:auto;display:flex;align-items:center;gap:1cqw;color:#fff}
78
+ .rar svg{width:3cqw;height:3cqw;fill:currentColor}
79
+ .holo,.glitter,.glare{position:absolute;inset:0;border-radius:inherit;pointer-events:none}
80
+ .holo{mix-blend-mode:color-dodge;opacity:var(--o);filter:brightness(.55) contrast(1.6) saturate(1.4);
81
+ background:repeating-linear-gradient(110deg,#ff3b6b 0%,#ffd23b 6%,#3bff9a 12%,#3bd6ff 18%,#a63bff 24%,#ff3b6b 30%),
82
+ repeating-linear-gradient(-45deg,#0e152e 0%,#8fa3bf 3.8%,#8fa3bf 4.5%,#0e152e 5.2%,#0e152e 10%);
83
+ background-size:400% 400%,300% 300%;background-blend-mode:overlay;
84
+ background-position:var(--mx) var(--my),calc(100% - var(--mx)) calc(100% - var(--my))}
85
+ .card[data-foil=holo] .holo{-webkit-mask:var(--artmask);mask:var(--artmask)}
86
+ .card[data-foil=gold] .holo{background:repeating-linear-gradient(115deg,#3a2500 0%,#ffe9a0 5%,#b8860b 9%,#3a2500 14%);background-size:300% 300%;filter:brightness(.7) contrast(1.4)}
87
+ .card[data-foil=cosmos] .holo{background:radial-gradient(circle at var(--mx) var(--my),#ff6be6 0%,transparent 35%),
88
+ repeating-radial-gradient(circle at 30% 70%,#2b2b8f 0,#8f2bd6 8%,#2bd6c4 16%,#2b2b8f 24%);background-size:100% 100%,200% 200%}
89
+ .glitter{mix-blend-mode:color-dodge;opacity:calc(var(--o)*.9);
90
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 9 -6.2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
91
+ background-size:220px;background-position:var(--mx) var(--my);
92
+ -webkit-mask:radial-gradient(circle at var(--mx) var(--my),#000 0%,rgb(0 0 0/.25) 55%,transparent 80%);mask:radial-gradient(circle at var(--mx) var(--my),#000 0%,rgb(0 0 0/.25) 55%,transparent 80%)}
93
+ .card[data-foil=none] .holo,.card[data-foil=none] .glitter{display:none}
94
+ .glare{mix-blend-mode:overlay;background:radial-gradient(farthest-corner circle at var(--mx) var(--my),rgb(255 255 255/.75) 0%,rgb(255 255 255/.15) 30%,rgb(0 0 0/.35) 90%);opacity:calc(.4 + var(--o)*.5)}
95
+ @media (prefers-reduced-motion:reduce){.rays{animation:none}}
96
+ `;
97
+
98
+ /**
99
+ * @param {HTMLElement} container
100
+ * @param {{ identity?: object, character?: object, id?: string, createdAt?: number, level?: number, model?: string,
101
+ * foil?: string, locale?: string, width?: number, interactive?: boolean }} [opts]
102
+ * character: spec a dibujar (por defecto identity.look.character). width: ancho máximo en px.
103
+ */
104
+ export function createTradingCard(container, opts = {}) {
105
+ const host = document.createElement('div');
106
+ host.className = 'ots-trading-card';
107
+ container.appendChild(host);
108
+ const root = host.attachShadow({ mode: 'open' });
109
+ let o = { ...opts };
110
+ let figure = null;
111
+ let raf = 0;
112
+ let hovering = false;
113
+ let destroyed = false;
114
+
115
+ const card = () => root.querySelector('.card');
116
+
117
+ function render() {
118
+ const id = o.identity || {};
119
+ const look = id.look || {};
120
+ const spec = o.character || look.character || {};
121
+ const name = id.name || 'ot';
122
+ const info = cardTraits(o.id, name, o.locale);
123
+ const foil = CARD_FOILS.includes(o.foil) ? o.foil : info.defaultFoil;
124
+ const C = hex(spec.body?.color, hex(look.color, '#6d5dfc'));
125
+ const A = hex(look.accent, hex(spec.cheeks?.color, '#22d3ee'));
126
+ const flavor = id.bio || id.tagline || '';
127
+ const year = new Date(o.createdAt || Date.now()).getFullYear();
128
+ const serial = `#${String(o.id || 'local').replace(/^ots_/, '').slice(-6).toUpperCase()} · ${year}`;
129
+ figure?.destroy?.();
130
+ figure = null;
131
+ root.innerHTML = `<style>${CSS}</style>
132
+ <div class="scene"><div class="card" data-foil="${foil}" style="--c:${C};--a:${A};${o.width ? `--w:${o.width}px` : ''}">
133
+ <div class="frame"><div class="face">
134
+ <header><span class="name">${esc(name)}</span>${o.level ? `<span class="lvl">LV <b>${esc(o.level)}</b></span>` : ''}</header>
135
+ <div class="art"><div class="rays"></div><div class="dots"></div><div class="floor"></div><div class="fig"></div></div>
136
+ <div class="type"><span>${esc(id.role || 'ot')}</span><span>${esc(String(o.model || '').slice(0, 24))}</span></div>
137
+ <div class="stats">${info.stats.map(([n, v]) => `<div class="stat"><span>${esc(n)}</span><div class="bar"><i style="width:${v}%"></i></div><em>${v}</em></div>`).join('')}</div>
138
+ ${flavor ? `<div class="flavor">${esc(flavor)}</div>` : ''}
139
+ <footer><span>${esc(serial)}</span><span class="rar">${STAR.repeat(info.rarity + 1)}<span>${esc(info.rarityName)}</span></span></footer>
140
+ </div></div>
141
+ <div class="holo"></div><div class="glitter"></div><div class="glare"></div>
142
+ </div></div>`;
143
+ const fig = root.querySelector('.fig');
144
+ if (look.kind === 'image' && look.image) fig.innerHTML = `<img src="${esc(look.image)}" alt="" referrerpolicy="no-referrer">`;
145
+ else {
146
+ figure = createCharacter(fig, spec, { locale: o.locale, title: name, moodFx: false });
147
+ figure.mood?.('happy', { fx: false });
148
+ }
149
+ requestAnimationFrame(setMask);
150
+ }
151
+
152
+ // la lámina holo clásica solo brilla en el arte: máscara con la ventana del arte recortada
153
+ function setMask() {
154
+ const c = card()?.getBoundingClientRect();
155
+ const a = root.querySelector('.art')?.getBoundingClientRect();
156
+ if (!c?.width || !a) return;
157
+ const [x, y, w, h] = [a.left - c.left, a.top - c.top, a.width, a.height].map((v) => (v / c.width) * 100);
158
+ const H = (c.height / c.width) * 100;
159
+ card().style.setProperty('--artmask', `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 ${H}' preserveAspectRatio='none'><rect x='${x}' y='${y}' width='${w}' height='${h}' rx='2'/></svg>`)}") 0 0 / 100% 100%`);
160
+ }
161
+
162
+ function light(px, py) {
163
+ const el = card();
164
+ if (!el) return;
165
+ el.style.setProperty('--mx', `${px * 100}%`);
166
+ el.style.setProperty('--my', `${py * 100}%`);
167
+ el.style.setProperty('--ry', `${(px - 0.5) * 26}deg`);
168
+ el.style.setProperty('--rx', `${(0.5 - py) * 26}deg`);
169
+ el.style.setProperty('--o', String(0.35 + Math.min(1, Math.hypot(px - 0.5, py - 0.5) * 1.6) * 0.65));
170
+ }
171
+
172
+ render();
173
+ const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setMask) : null;
174
+ ro?.observe(host);
175
+ const still = o.interactive === false || matchMedia('(prefers-reduced-motion: reduce)').matches;
176
+ if (o.interactive !== false) {
177
+ root.addEventListener('pointermove', (e) => {
178
+ const el = card();
179
+ const b = el.getBoundingClientRect();
180
+ hovering = true;
181
+ el.classList.add('live');
182
+ light((e.clientX - b.left) / b.width, (e.clientY - b.top) / b.height);
183
+ });
184
+ root.addEventListener('pointerleave', () => {
185
+ hovering = false;
186
+ card()?.classList.remove('live');
187
+ });
188
+ }
189
+ if (still) light(0.68, 0.3), card().style.setProperty('--rx', '0deg'), card().style.setProperty('--ry', '0deg');
190
+ else {
191
+ const t0 = performance.now();
192
+ const sway = (t) => {
193
+ if (destroyed) return;
194
+ if (!hovering) {
195
+ const s = (t - t0) / 1000;
196
+ light(0.5 + Math.sin(s * 0.7) * 0.28, 0.45 + Math.cos(s * 0.5) * 0.2);
197
+ }
198
+ raf = requestAnimationFrame(sway);
199
+ };
200
+ raf = requestAnimationFrame(sway);
201
+ }
202
+
203
+ return {
204
+ el: host,
205
+ /** Cambia datos (identity, character, foil…) y vuelve a pintar. */
206
+ update(next = {}) {
207
+ o = { ...o, ...next };
208
+ render();
209
+ },
210
+ setFoil(f) {
211
+ o.foil = f;
212
+ const el = card();
213
+ if (el) el.dataset.foil = CARD_FOILS.includes(f) ? f : cardTraits(o.id, o.identity?.name, o.locale).defaultFoil;
214
+ },
215
+ get foil() {
216
+ return card()?.dataset.foil;
217
+ },
218
+ destroy() {
219
+ destroyed = true;
220
+ cancelAnimationFrame(raf);
221
+ ro?.disconnect();
222
+ figure?.destroy?.();
223
+ host.remove();
224
+ },
225
+ };
226
+ }
@@ -0,0 +1,4 @@
1
+ /** Mods que trae 7ots (formato en ../mods.js). */
2
+ import orc from './orc.js';
3
+
4
+ export const MOD_CATALOG = [orc];
@@ -0,0 +1,64 @@
1
+ // Mod de ejemplo: orco (piel verde, cejas fruncidas, colmillos y orejas puntiagudas).
2
+ // Los glb salen de scripts/mod-glb.mjs (orc-tusks, orc-ears).
3
+ export default {
4
+ "id": "orc",
5
+ "v": 1,
6
+ "name": {
7
+ "es": "Orco",
8
+ "en": "Orc",
9
+ "pt": "Orc"
10
+ },
11
+ "by": "7ots",
12
+ "character": {
13
+ "body": {
14
+ "color": "#6f9a3e",
15
+ "patternColor": "#9cc06a"
16
+ },
17
+ "eyes": {
18
+ "iris": "#e0a312"
19
+ },
20
+ "brows": {
21
+ "type": "angled",
22
+ "color": "#2f3b1d"
23
+ },
24
+ "mouth": {
25
+ "type": "flat"
26
+ },
27
+ "cheeks": {
28
+ "type": "none"
29
+ }
30
+ },
31
+ "parts": [
32
+ {
33
+ "id": "tusks",
34
+ "anchor": "mouth",
35
+ "layer": "front",
36
+ "svg": "<path d=\"M8 3Q11 -5 16 -13Q18.5 -4 14 5Z\" fill=\"{color}\" stroke=\"{line}\" stroke-width=\"1.6\" stroke-linejoin=\"round\"/><path d=\"M12 0Q14 -6 16 -10\" stroke=\"#fff\" stroke-width=\"1.4\" stroke-linecap=\"round\" fill=\"none\" opacity=\".55\"/><g transform=\"scale(-1 1)\"><path d=\"M8 3Q11 -5 16 -13Q18.5 -4 14 5Z\" fill=\"{color}\" stroke=\"{line}\" stroke-width=\"1.6\" stroke-linejoin=\"round\"/><path d=\"M12 0Q14 -6 16 -10\" stroke=\"#fff\" stroke-width=\"1.4\" stroke-linecap=\"round\" fill=\"none\" opacity=\".55\"/></g>",
37
+ "view": [
38
+ -20,
39
+ -16,
40
+ 40,
41
+ 24
42
+ ],
43
+ "glb": "data:model/gltf-binary;base64,Z2xURgIAAADcOAAAsAMAAEpTT057ImFzc2V0Ijp7InZlcnNpb24iOiIyLjAiLCJnZW5lcmF0b3IiOiI3b3RzIG1vZC1nbGIifSwic2NlbmUiOjAsInNjZW5lcyI6W3sibm9kZXMiOlswXX1dLCJub2RlcyI6W3sibWVzaCI6MH1dLCJtZXNoZXMiOlt7InByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjAsIk5PUk1BTCI6MX0sImluZGljZXMiOjIsIm1hdGVyaWFsIjowfV19XSwibWF0ZXJpYWxzIjpbeyJwYnJNZXRhbGxpY1JvdWdobmVzcyI6eyJiYXNlQ29sb3JGYWN0b3IiOlswLjg2NzEzNTUzNzUyMDkwNDcsMC43OTY5MTY1NDI5MDc5NzgxLDAuNTczMTU4ODc1MDY3NTIzMywxXSwibWV0YWxsaWNGYWN0b3IiOjAsInJvdWdobmVzc0ZhY3RvciI6MC4zNX19XSwiYnVmZmVycyI6W3siYnl0ZUxlbmd0aCI6MTM1ODR9XSwiYnVmZmVyVmlld3MiOlt7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6MCwiYnl0ZUxlbmd0aCI6NDcwNCwidGFyZ2V0IjozNDk2Mn0seyJidWZmZXIiOjAsImJ5dGVPZmZzZXQiOjQ3MDQsImJ5dGVMZW5ndGgiOjQ3MDQsInRhcmdldCI6MzQ5NjJ9LHsiYnVmZmVyIjowLCJieXRlT2Zmc2V0Ijo5NDA4LCJieXRlTGVuZ3RoIjo0MTc2LCJ0YXJnZXQiOjM0OTYzfV0sImFjY2Vzc29ycyI6W3siYnVmZmVyVmlldyI6MCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjM5MiwidHlwZSI6IlZFQzMiLCJtaW4iOlstMTYuMjkxNTQxMzEyMDUzNTcsLTcuMjMwNTg2Mzg4MTk5MzI3NSwtMi4xODgyMTk3ODI1NjY2MDIzXSwibWF4IjpbMTYuMjkxNTQxMzEyMDUzNTcsMTEuMTkzNjU4NjI1ODQ5OTI4LDUuNDE1MzA1OTM4OTIxOTg1XX0seyJidWZmZXJWaWV3IjoxLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MzkyLCJ0eXBlIjoiVkVDMyJ9LHsiYnVmZmVyVmlldyI6MiwiY29tcG9uZW50VHlwZSI6NTEyMywiY291bnQiOjIwODgsInR5cGUiOiJTQ0FMQVIifV19IBA1AABCSU4An/VFQYfRzcAAAIA/s4c9QaS23sDlBSZApOYoQfdg58CGtXBAgZkNQRp+5cDlBYZADOLlQG+P2cCGtXBAnSPAQHbHxsDlBSZAwxS0QHkussAAAIA/mvDEQFxJocCWFxi/tzLuQAmfmMAMa+G/f2YSQeaBmsDLCwzA+g4tQZFwpsAMa+G/Me4/QYo4ucCWFxi/n/VFQYfRzcAAAIA/gN1DQQHNq8AQjbU/Q+o7QZHKucBO7ThAdOsoQTOav8DC2X1AEfgPQUutu8AKiotALn/vQB0Rr8DC2X1A4H7NQKUmncBO7ThAiQvDQNW6isAQjbU/A/LSQIp6ecB1D1i9n+/4QEbbbcBkmZC/M2sVQRa1dcAHDsO/raMtQbp2h8BkmZC/1KM+QTFhmcB1D1i9gN1DQQHNq8AQjbU/XSZDQe0gicBjfeg/QKk7QfOHlMARlEtAxCIqQbHOl8BmwYVAEkUTQWsUksC4dJFAS2H6QAPihMBmwYVAIaDbQG2BZ8ARlEtAQITSQCCPRsBjfeg/e37hQBXBL8CTSuc+uUUCQZgzKcClFQy/bCMZQSWoNMA0sGm/2DcvQfUMT8ClFQy/bZg+QY1PccCTSuc+XSZDQe0gicBjfeg/bHNDQaPoSsB9aAxAUXg8QVcmXcALLF1AJGUsQXdxX8DNJYxAjYgXQbIsUcDN95ZAjnkDQcoqNsDNJYxAJTDrQFGoFcALLF1AgTjjQJG28L99aAxAty7xQCk7zL++k24/iKoIQeikx7/1YAU8H4cdQXIu5L/19Ki+HpYxQSEZDcD1YAU8mnc/QZqbLcC+k24/bHNDQaPoSsB9aAxAK7ZEQcqgAcDrQyNAm0c+QRfeD8APk21AnaUvQfenDsB8/JFA67scQQaj/L8a8ZtA25sKQdd2xr98/JFAcUH8QFdPib8Pk21ATUX1QM4eK7/rQyNANhEBQTZT5L6O6bE/NLMPQTUE7r58Owo/5pwiQelbOL8qWmo+9rw0QSRakr98Owo/GThBQaSBz7+O6bE/K7ZEQcqgAcDrQyNA6elGQSD9Wb/70DhA0g9BQeaJgr/munxAOtwzQUUPdb8jOZdAsdgiQUY6Lr9pUqBARpQSQREfh74jOZdA1WoHQYpwOT7munxAAloEQVkCCT/70DhAGjQKQQUZND8fzuk/sWcXQX4UJD9fX4Y/OmsoQf9+uj6S9EM/pa84QeGzVr1fX4Y/FtlDQdKt/r4fzuk/6elGQSD9Wb/70DhABQ1KQQEysz6sD01AfsxEQU5aaT5OToVA5AI5QQWvpT6+1ZtAx9gpQfmKHD/GFKRAXV4bQbnYgT++1ZtAxnQRQatTtj9OToVA9sMOQXCm3T+sD01AfQQUQadH7T+9gg9AFs4fQS8H4T+458Q/M/guQXQtvD+a66M/nnI9QTeaiD+458Q/NFxHQYs+KD+9gg9ABQ1KQQEysz6sD01A+R5OQVX0yT8AAGBAZnpJQfJEvz9bmotAiBQ/QcyAzj9Kz59An7YxQe6S8z+3NKdAkPUkQTxGEkBKz59AOTwcQf4mKkBbmotAB+EZQdYFO0AAAGBAmoUeQYddQEBJyyhAeOsoQZq/OEBsYQBAYUk2QYk2JkAlLeM/cApDQcS5DUBsYQBAx8NLQQSy6z9JyyhA+R5OQVX0yT8AAGBAmx9TQT4nNUD2oXFA6hZPQTrTMUDPQJFAxgxGQTFYOkBlJKNAQ206Qb5tTECksKlAqVUvQWY7Y0BlJKNA1r4nQfekeEDPQJFAYLElQdR2g0D2oXFAEbopQdYghUBMwkBANcQyQVvegEAg+xxAuGM+QSinb0Cj4g9AUntJQYDZWEAg+xxAJBJRQe9vQ0BMwkBAmx9TQT4nNUD2oXFAzw5ZQXxsg0DH+oBAyJ9VQWWNgkBpQZZA0edNQYT9hkCg1KVAYPhDQW2Mj0AMiKtA+Ho6QRLvmUCg1KVAjvozQRJdo0BpQZZAHDUyQZhPqUDH+oBAI6Q1QbAuqkBHaFdAGlw9QZG+pUDZQThAi0tHQagvnUAB2yxA88hQQQPNkkDZQThAXUlXQQNfiUBHaFdAzw5ZQXxsg0DH+oBAXuxfQef9rEBjfYhA+hJdQae2rEA6nJpAXqJWQfkTsUAY4KdASVROQXnquEASu6xAaGJGQYUgwkAY4KdAt+1AQUU+ykA6nJpAc2w/Qf8Wz0BjfYhA10VCQT9ez0AYvWxAc7ZIQe0Ay0BcNVJAiARRQW0qw0Bqf0hAafZYQWH0uUBcNVJAGmteQaHWsUAYvWxAXuxfQef9rEBjfYhA7rdnQaM+10DRWI9AqW5lQRRg10CAUZ5AmDlgQYFv20A5R6lA631ZQWVW4kAwSq1AgAlTQVE76kA5R6lAGJdOQcMA8UCAUZ5AvldNQUPW9EDRWI9AA6FPQdG09EAiYIBAFdZUQWWl8EDT1GpAwZFbQYG+6UDlzmJALQZiQZXZ4UDT1GpAlHhmQSIU20AiYIBA7rdnQaM+10DRWI9A/HBwQQgTAUEQjZVAJbFuQXtCAUGQYaFAE6tqQUkIA0GVCqpAynJlQdnqBUERNq1AVm5gQUYkCUGVCqpA5PVcQWTXC0GQYaFAgfdbQQNLDUEQjZVAWbddQZAbDUGPuIlAar1hQcFVC0GKD4FAtPVmQTFzCEEdyHtAJ/prQcQ5BUGKD4FAmnJvQaeGAkGPuIlA/HBwQQgTAUEQjZVA4BZ6QRXWFkEfGptA39h4QY4QF0HIzKNAyfR1Qfh7GEHPKqpA6zByQfO2GkFwf6xAio9uQYAoHUHPKqpArglsQQEpH0HIzKNAZExrQSQvIEEfGptAZYpsQav0H0F3Z5JAe25vQUGJHkFwCYxAWTJzQUdOHEHOtIlAutN2QbrcGUFwCYxAlll5QTjcF0F3Z5JA4BZ6QRXWFkEfGptAFFWCQcbmLEEAAKBAnvKBQXIZLUFfk6VAjQqBQRUTLkFHqKlAIrZ/QcyQL0G+JqtA9Gp9QU8sMUFHqKlA5tB7QVp3MkFfk6VA2VV7QToZM0EAAKBAxBp8QY7mMkGhbJpA5ep9QevsMUG5V5ZA7ySAQTRvMEFC2ZRAhkqBQbHTLkG5V5ZAjReCQaaILUGhbJpAFFWCQcbmLEEAAKBAAAAQQQAAwMAAAIA/n/VFwYfRzcAAAIA/s4c9waS23sDlBSZApOYowfdg58CGtXBAgZkNwRp+5cDlBYZADOLlwG+P2cCGtXBAnSPAwHbHxsDlBSZAwxS0wHkussAAAIA/mvDEwFxJocCWFxi/tzLuwAmfmMAMa+G/f2YSweaBmsDLCwzA+g4twZFwpsAMa+G/Me4/wYo4ucCWFxi/n/VFwYfRzcAAAIA/gN1DwQHNq8AQjbU/Q+o7wZHKucBO7ThAdOsowTOav8DC2X1AEfgPwUutu8AKiotALn/vwB0Rr8DC2X1A4H7NwKUmncBO7ThAiQvDwNW6isAQjbU/A/LSwIp6ecB1D1i9n+/4wEbbbcBkmZC/M2sVwRa1dcAHDsO/raMtwbp2h8BkmZC/1KM+wTFhmcB1D1i9gN1DwQHNq8AQjbU/XSZDwe0gicBjfeg/QKk7wfOHlMARlEtAxCIqwbHOl8BmwYVAEkUTwWsUksC4dJFAS2H6wAPihMBmwYVAIaDbwG2BZ8ARlEtAQITSwCCPRsBjfeg/e37hwBXBL8CTSuc+uUUCwZgzKcClFQy/bCMZwSWoNMA0sGm/2DcvwfUMT8ClFQy/bZg+wY1PccCTSuc+XSZDwe0gicBjfeg/bHNDwaPoSsB9aAxAUXg8wVcmXcALLF1AJGUswXdxX8DNJYxAjYgXwbIsUcDN95ZAjnkDwcoqNsDNJYxAJTDrwFGoFcALLF1AgTjjwJG28L99aAxAty7xwCk7zL++k24/iKoIweikx7/1YAU8H4cdwXIu5L/19Ki+HpYxwSEZDcD1YAU8mnc/wZqbLcC+k24/bHNDwaPoSsB9aAxAK7ZEwcqgAcDrQyNAm0c+wRfeD8APk21AnaUvwfenDsB8/JFA67scwQaj/L8a8ZtA25sKwdd2xr98/JFAcUH8wFdPib8Pk21ATUX1wM4eK7/rQyNANhEBwTZT5L6O6bE/NLMPwTUE7r58Owo/5pwiwelbOL8qWmo+9rw0wSRakr98Owo/GThBwaSBz7+O6bE/K7ZEwcqgAcDrQyNA6elGwSD9Wb/70DhA0g9BweaJgr/munxAOtwzwUUPdb8jOZdAsdgiwUY6Lr9pUqBARpQSwREfh74jOZdA1WoHwYpwOT7munxAAloEwVkCCT/70DhAGjQKwQUZND8fzuk/sWcXwX4UJD9fX4Y/Omsowf9+uj6S9EM/pa84weGzVr1fX4Y/FtlDwdKt/r4fzuk/6elGwSD9Wb/70DhABQ1KwQEysz6sD01AfsxEwU5aaT5OToVA5AI5wQWvpT6+1ZtAx9gpwfmKHD/GFKRAXV4bwbnYgT++1ZtAxnQRwatTtj9OToVA9sMOwXCm3T+sD01AfQQUwadH7T+9gg9AFs4fwS8H4T+458Q/M/guwXQtvD+a66M/nnI9wTeaiD+458Q/NFxHwYs+KD+9gg9ABQ1KwQEysz6sD01A+R5OwVX0yT8AAGBAZnpJwfJEvz9bmotAiBQ/wcyAzj9Kz59An7Yxwe6S8z+3NKdAkPUkwTxGEkBKz59AOTwcwf4mKkBbmotAB+EZwdYFO0AAAGBAmoUewYddQEBJyyhAeOsowZq/OEBsYQBAYUk2wYk2JkAlLeM/cApDwcS5DUBsYQBAx8NLwQSy6z9JyyhA+R5OwVX0yT8AAGBAmx9TwT4nNUD2oXFA6hZPwTrTMUDPQJFAxgxGwTFYOkBlJKNAQ206wb5tTECksKlAqVUvwWY7Y0BlJKNA1r4nwfekeEDPQJFAYLElwdR2g0D2oXFAEbopwdYghUBMwkBANcQywVvegEAg+xxAuGM+wSinb0Cj4g9AUntJwYDZWEAg+xxAJBJRwe9vQ0BMwkBAmx9TwT4nNUD2oXFAzw5ZwXxsg0DH+oBAyJ9VwWWNgkBpQZZA0edNwYT9hkCg1KVAYPhDwW2Mj0AMiKtA+Ho6wRLvmUCg1KVAjvozwRJdo0BpQZZAHDUywZhPqUDH+oBAI6Q1wbAuqkBHaFdAGlw9wZG+pUDZQThAi0tHwagvnUAB2yxA88hQwQPNkkDZQThAXUlXwQNfiUBHaFdAzw5ZwXxsg0DH+oBAXuxfwef9rEBjfYhA+hJdwae2rEA6nJpAXqJWwfkTsUAY4KdASVROwXnquEASu6xAaGJGwYUgwkAY4KdAt+1AwUU+ykA6nJpAc2w/wf8Wz0BjfYhA10VCwT9ez0AYvWxAc7ZIwe0Ay0BcNVJAiARRwW0qw0Bqf0hAafZYwWH0uUBcNVJAGmtewaHWsUAYvWxAXuxfwef9rEBjfYhA7rdnwaM+10DRWI9AqW5lwRRg10CAUZ5AmDlgwYFv20A5R6lA631ZwWVW4kAwSq1AgAlTwVE76kA5R6lAGJdOwcMA8UCAUZ5AvldNwUPW9EDRWI9AA6FPwdG09EAiYIBAFdZUwWWl8EDT1GpAwZFbwYG+6UDlzmJALQZiwZXZ4UDT1GpAlHhmwSIU20AiYIBA7rdnwaM+10DRWI9A/HBwwQgTAUEQjZVAJbFuwXtCAUGQYaFAE6tqwUkIA0GVCqpAynJlwdnqBUERNq1AVm5gwUYkCUGVCqpA5PVcwWTXC0GQYaFAgfdbwQNLDUEQjZVAWbddwZAbDUGPuIlAar1hwcFVC0GKD4FAtPVmwTFzCEEdyHtAJ/prwcQ5BUGKD4FAmnJvwaeGAkGPuIlA/HBwwQgTAUEQjZVA4BZ6wRXWFkEfGptA39h4wY4QF0HIzKNAyfR1wfh7GEHPKqpA6zBywfO2GkFwf6xAio9uwYAoHUHPKqpArglswQEpH0HIzKNAZExrwSQvIEEfGptAZYpswav0H0F3Z5JAe25vwUGJHkFwCYxAWTJzwUdOHEHOtIlAutN2wbrcGUFwCYxAlll5wTjcF0F3Z5JA4BZ6wRXWFkEfGptAFFWCwcbmLEEAAKBAnvKBwXIZLUFfk6VAjQqBwRUTLkFHqKlAIrZ/wcyQL0G+JqtA9Gp9wU8sMUFHqKlA5tB7wVp3MkFfk6VA2VV7wToZM0EAAKBAxBp8wY7mMkGhbJpA5ep9wevsMUG5V5ZA7ySAwTRvMEFC2ZRAhkqBwbHTLkG5V5ZAjReCwaaILUGhbJpAFFWCwcbmLEEAAKBAAAAQwQAAwMAAAIA/Re19P2QOAr4AAAAAWEJWP6mIkL7gDfA+olzqPtRPub6e5E8/zrs0vYlvsL7gDXA/9L4Iv0SRcL6e5E8/FY5hv/o2f73gDfA+Re19v2QOAj7FZQQlWEJWv6mIkD7gDfC+olzqvtRPuT6e5E+/zrs0PYlvsD7gDXC/9L4IP0SRcD6e5E+/FY5hP/o2fz3gDfC+Re19P2QOAr7FZYSlynR8PybLKb4AAAAAcqNTPxO6nL5jsvE+hjnkPuWPur7LUFE/UddfvX5opr5jsnE/B1gKv6VUS77LUFE/56Bhv7PmmrxjsvE+ynR8vybLKT6zTQUlcqNTvxO6nD5jsvG+hjnkvuWPuj7LUFG/UddfPX5opj5jsnG/B1gKP6VUSz7LUFG/56BhP7PmmjxjsvG+ynR8PybLKb6zTYWlPYJ6P2n0Ur4AAAAADMVQP5kHqb5DYfM+mS7ePmdKu77xxVI/5tWCvQFem75DYXM/8WoLv2SgI77xxVI/yR9hv4GZ2jxDYfM+PYJ6v2n0Uj5XOwYlDMVQv5kHqT5DYfO+mS7evmdKuz7xxVK/5tWCPQFemz5DYXO/8WoLP2SgIz7xxVK/yR9hP4GZ2rxDYfO+PYJ6P2n0Ur5XO4alnzp4PzNjer4AAAAAv+FNPzGFtL6o9vQ+07zYPv95u74GJVQ/2XORvdkykL6o9nQ/NtwLv5Uh+b0GJVQ/OxBgv2BJkT2o9vQ+nzp4vzNjej7tGgclv+FNvzGFtD6o9vS+07zYvv95uz4GJVS/2XORPdkykD6o9nS/NtwLP5Uh+T0GJVS/OxBgP2BJkb2o9vS+nzp4PzNjer7tGoelN6p1P64CkL4AAAAA/ABLP+A7v771b/Y+aeXTPmI3u77Ga1U/pvibvcoIhb71b3Y/g7cLv9LSrL3Ga1U/EYBev1jM6D31b/Y+N6p1v64CkD4F6wcl/ABLv+A7vz71b/a+aeXTvmI3uz7Ga1W/pvibPcoIhT71b3a/g7cLP9LSrD3Ga1W/EYBeP1jM6L31b/a+N6p1P64CkL4F64el6dxyP5voob4AAAAA1yhIP2s2yb5qy/c+qaXPPg2aur6umFY/qKeivXz7c75qy3c/FAoLv5OLRb2umFY/zH1cv1pxHj5qy/c+6dxyv5vooT6nqggl1yhIv2s2yT5qy/e+qaXPvg2auj6umFa/qKeiPXz7cz5qy3e/FAoLP5OLRT2umFa/zH1cP1pxHr5qy/e+6dxyP5voob6nqoil791vP0Lisr4AAAAAHV5FP6eA0r4aCPk+t/fLPqC3ub7xqlc/zs+lvaZWXr4aCHk/FOIJv9arWrzxqlc/Fhhav6eqRj4aCPk+791vv0Lisj5RWQklHV5Fv6eA0j4aCPm+t/fLvqC3uT7xqle/zs+lPaZWXj4aCHm/FOIJP9arWjzxqle/FhhaP6eqRr4aCPm+791vP0Lisr5RWYmltLdsP6Pxwr4AAAAAbqRCP+cm277WJfo+VdPIPnCjuL5lolg/R8elvWhNSb7WJXo/Ck4IvzHjpDxlolg/Vl1Xv2YAbT7WJfo+tLdsv6Pxwj7p9gklbqRCv+cm2z7WJfq+VdPIvnCjuD5loli/R8elPWhNST7WJXq/Ck4IPzHjpLxloli/Vl1XP2YAbb7WJfq+tLdsP6Pxwr7p9omlsHNpP1Ib0r4AAAAAZv4/P4Q1474MJfs+mC7GPlBut75pf1k/meeivVQBNb4MJXs/ZFwGvw5oVT1pf1k/WVtUv9q0iD4MJfs+sHNpv1Ib0j6qgwolZv4/v4Q14z4MJfu+mC7GvlButz5pf1m/meeiPVQBNT4MJXu/ZFwGPw5oVb1pf1m/WVtUP9q0iL4MJfu+sHNpP1Ib0r6qg4qlXhpmP+Fl4L4AAAAAw209P4e46r6sBvw+lP7DPoEmtr7PQlo/ZoqdvcCLIb6sBnw/FBsEv3/9qD3PQlo/Dx9Rv6fymT6sBvw+Xhpmv+Fl4D4bAAslw209v4e46j6sBvy+lP7DvoEmtj7PQlq/ZoqdPcCLIT6sBny/FBsEP3/9qL3PQlq/Dx9RP6fymb6sBvy+XhpmP+Fl4L4bAIulObNiP2rZ7b4AAAAAjfM6P2i78b4MzPw+5zfCPsrXtL6+7Vo/1AaWvYL+Dr4MzHw/RpcBv4AG5D2+7Vo/Z7RNvyc8qj4MzPw+ObNiv2rZ7T73bAsljfM6v2i78T4MzPy+5zfCvsrXtD6+7Vq/1AaWPYL+Dj4MzHy/RpcBP4AG5L2+7Vq/Z7RNPyc8qr4MzPy+ObNiP2rZ7b73bIulxERfPxt/+r4AAAAAPZA4P+pI+L7Ldv0+Kc/APqGLs76cgVs/mK+MvbbJ+r3Ldn0/X7r9vvLmDT6cgVs/MCZKv32WuT7Ldv0+xERfvxt/+j4iywslPZA4v+pI+D7Ldv2+Kc/AvqGLsz6cgVu/mK+MPbbJ+j3Ldn2/X7r9PvLmDb6cgVu/MCZKP32Wub7Ldv2+xERfPxt/+r4iy4ulltRbP2owA78AAAAA2kM2PwVr/r61CP4+Sbm/PmhJsr76/1s/rNGBvduI2b21CH4/5O/3vtkuKD76/1s/D35Gv88IyD61CP4+ltRbv2owAz+cGwwl2kM2vwVr/j61CP6+Sbm/vmhJsj76/1u/rNGBPduI2T21CH6/5O/3PtkuKL76/1u/D35GP88IyL61CP6+ltRbP2owA7+cG4ylbWdYP3DECL8AAAAAGA40P28VAr+zg/4+xuu+PqwWsb5+alw/6mZrvVs8ur2zg34/FOPxvmjkQD5+alw/h8RCv8eb1T6zg/4+bWdYv3DECD9yXwwlGA40v28VAj+zg/6+xuu+vqwWsT5+aly/6mZrPVs8uj2zg36/FOPxPmjkQL5+aly/h8RCP8eb1b6zg/6+bWdYP3DECL9yX4yl3D1VP7+lDb8AAAAAyhMyP2eZBL834/4+BGW+PtUKsL42vVw/Vw1TvfLcnr03434/tRbsvlOBVj42vVw/oEQ/v5J74T434/4+3D1Vv7+lDT8glAwlyhMyv2eZBD834/6+BGW+vtUKsD42vVy/Vw1TPfLcnj03436/tRbsPlOBVr42vVy/oEQ/P5J74b434/6+3D1VP7+lDb8glIylPhPuvT4Tbr9ujrK+Re19v2QOAr4AAAAAWEJWv6mIkL7gDfA+olzqvtRPub6e5E8/zrs0PYlvsL7gDXA/9L4IP0SRcL6e5E8/FY5hP/o2f73gDfA+Re19P2QOAj7FZQQlWEJWP6mIkD7gDfC+olzqPtRPuT6e5E+/zrs0vYlvsD7gDXC/9L4Iv0SRcD6e5E+/FY5hv/o2fz3gDfC+Re19v2QOAr7FZYSlynR8vybLKb4AAAAAcqNTvxO6nL5jsvE+hjnkvuWPur7LUFE/UddfPX5opr5jsnE/B1gKP6VUS77LUFE/56BhP7PmmrxjsvE+ynR8PybLKT6zTQUlcqNTPxO6nD5jsvG+hjnkPuWPuj7LUFG/UddfvX5opj5jsnG/B1gKv6VUSz7LUFG/56Bhv7PmmjxjsvG+ynR8vybLKb6zTYWlPYJ6v2n0Ur4AAAAADMVQv5kHqb5DYfM+mS7evmdKu77xxVI/5tWCPQFem75DYXM/8WoLP2SgI77xxVI/yR9hP4GZ2jxDYfM+PYJ6P2n0Uj5XOwYlDMVQP5kHqT5DYfO+mS7ePmdKuz7xxVK/5tWCvQFemz5DYXO/8WoLv2SgIz7xxVK/yR9hv4GZ2rxDYfO+PYJ6v2n0Ur5XO4alnzp4vzNjer4AAAAAv+FNvzGFtL6o9vQ+07zYvv95u74GJVQ/2XORPdkykL6o9nQ/NtwLP5Uh+b0GJVQ/OxBgP2BJkT2o9vQ+nzp4PzNjej7tGgclv+FNPzGFtD6o9vS+07zYPv95uz4GJVS/2XORvdkykD6o9nS/NtwLv5Uh+T0GJVS/OxBgv2BJkb2o9vS+nzp4vzNjer7tGoelN6p1v64CkL4AAAAA/ABLv+A7v771b/Y+aeXTvmI3u77Ga1U/pvibPcoIhb71b3Y/g7cLP9LSrL3Ga1U/EYBeP1jM6D31b/Y+N6p1P64CkD4F6wcl/ABLP+A7vz71b/a+aeXTPmI3uz7Ga1W/pvibvcoIhT71b3a/g7cLv9LSrD3Ga1W/EYBev1jM6L31b/a+N6p1v64CkL4F64el6dxyv5voob4AAAAA1yhIv2s2yb5qy/c+qaXPvg2aur6umFY/qKeiPXz7c75qy3c/FAoLP5OLRb2umFY/zH1cP1pxHj5qy/c+6dxyP5vooT6nqggl1yhIP2s2yT5qy/e+qaXPPg2auj6umFa/qKeivXz7cz5qy3e/FAoLv5OLRT2umFa/zH1cv1pxHr5qy/e+6dxyv5voob6nqoil791vv0Lisr4AAAAAHV5Fv6eA0r4aCPk+t/fLvqC3ub7xqlc/zs+lPaZWXr4aCHk/FOIJP9arWrzxqlc/FhhaP6eqRj4aCPk+791vP0Lisj5RWQklHV5FP6eA0j4aCPm+t/fLPqC3uT7xqle/zs+lvaZWXj4aCHm/FOIJv9arWjzxqle/Fhhav6eqRr4aCPm+791vv0Lisr5RWYmltLdsv6Pxwr4AAAAAbqRCv+cm277WJfo+VdPIvnCjuL5lolg/R8elPWhNSb7WJXo/Ck4IPzHjpDxlolg/Vl1XP2YAbT7WJfo+tLdsP6Pxwj7p9gklbqRCP+cm2z7WJfq+VdPIPnCjuD5loli/R8elvWhNST7WJXq/Ck4IvzHjpLxloli/Vl1Xv2YAbb7WJfq+tLdsv6Pxwr7p9omlsHNpv1Ib0r4AAAAAZv4/v4Q1474MJfs+mC7GvlBut75pf1k/meeiPVQBNb4MJXs/ZFwGPw5oVT1pf1k/WVtUP9q0iD4MJfs+sHNpP1Ib0j6qgwolZv4/P4Q14z4MJfu+mC7GPlButz5pf1m/meeivVQBNT4MJXu/ZFwGvw5oVb1pf1m/WVtUv9q0iL4MJfu+sHNpv1Ib0r6qg4qlXhpmv+Fl4L4AAAAAw209v4e46r6sBvw+lP7DvoEmtr7PQlo/ZoqdPcCLIb6sBnw/FBsEP3/9qD3PQlo/Dx9RP6fymT6sBvw+XhpmP+Fl4D4bAAslw209P4e46j6sBvy+lP7DPoEmtj7PQlq/ZoqdvcCLIT6sBny/FBsEv3/9qL3PQlq/Dx9Rv6fymb6sBvy+Xhpmv+Fl4L4bAIulObNiv2rZ7b4AAAAAjfM6v2i78b4MzPw+5zfCvsrXtL6+7Vo/1AaWPYL+Dr4MzHw/RpcBP4AG5D2+7Vo/Z7RNPyc8qj4MzPw+ObNiP2rZ7T73bAsljfM6P2i78T4MzPy+5zfCPsrXtD6+7Vq/1AaWvYL+Dj4MzHy/RpcBv4AG5L2+7Vq/Z7RNvyc8qr4MzPy+ObNiv2rZ7b73bIulxERfvxt/+r4AAAAAPZA4v+pI+L7Ldv0+Kc/AvqGLs76cgVs/mK+MPbbJ+r3Ldn0/X7r9PvLmDT6cgVs/MCZKP32WuT7Ldv0+xERfPxt/+j4iywslPZA4P+pI+D7Ldv2+Kc/APqGLsz6cgVu/mK+MvbbJ+j3Ldn2/X7r9vvLmDb6cgVu/MCZKv32Wub7Ldv2+xERfvxt/+r4iy4ulltRbv2owA78AAAAA2kM2vwVr/r61CP4+Sbm/vmhJsr76/1s/rNGBPduI2b21CH4/5O/3PtkuKD76/1s/D35GP88IyD61CP4+ltRbP2owAz+cGwwl2kM2PwVr/j61CP6+Sbm/PmhJsj76/1u/rNGBvduI2T21CH6/5O/3vtkuKL76/1u/D35Gv88IyL61CP6+ltRbv2owA7+cG4ylbWdYv3DECL8AAAAAGA40v28VAr+zg/4+xuu+vqwWsb5+alw/6mZrPVs8ur2zg34/FOPxPmjkQD5+alw/h8RCP8eb1T6zg/4+bWdYP3DECD9yXwwlGA40P28VAj+zg/6+xuu+PqwWsT5+aly/6mZrvVs8uj2zg36/FOPxvmjkQL5+aly/h8RCv8eb1b6zg/6+bWdYv3DECL9yX4yl3D1Vv7+lDb8AAAAAyhMyv2eZBL834/4+BGW+vtUKsL42vVw/Vw1TPfLcnr03434/tRbsPlOBVj42vVw/oEQ/P5J74T434/4+3D1VP7+lDT8glAwlyhMyP2eZBD834/6+BGW+PtUKsD42vVy/Vw1TvfLcnj03436/tRbsvlOBVr42vVy/oEQ/v5J74b434/6+3D1Vv7+lDb8glIylPhPuPT4Tbr9ujrK+AAANAAEAAQANAA4AAQAOAAIAAgAOAA8AAgAPAAMAAwAPABAAAwAQAAQABAAQABEABAARAAUABQARABIABQASAAYABgASABMABgATAAcABwATABQABwAUAAgACAAUABUACAAVAAkACQAVABYACQAWAAoACgAWABcACgAXAAsACwAXABgACwAYAAwADAAYABkADQAaAA4ADgAaABsADgAbAA8ADwAbABwADwAcABAAEAAcAB0AEAAdABEAEQAdAB4AEQAeABIAEgAeAB8AEgAfABMAEwAfACAAEwAgABQAFAAgACEAFAAhABUAFQAhACIAFQAiABYAFgAiACMAFgAjABcAFwAjACQAFwAkABgAGAAkACUAGAAlABkAGQAlACYAGgAnABsAGwAnACgAGwAoABwAHAAoACkAHAApAB0AHQApACoAHQAqAB4AHgAqACsAHgArAB8AHwArACwAHwAsACAAIAAsAC0AIAAtACEAIQAtAC4AIQAuACIAIgAuAC8AIgAvACMAIwAvADAAIwAwACQAJAAwADEAJAAxACUAJQAxADIAJQAyACYAJgAyADMAJwA0ACgAKAA0ADUAKAA1ACkAKQA1ADYAKQA2ACoAKgA2ADcAKgA3ACsAKwA3ADgAKwA4ACwALAA4ADkALAA5AC0ALQA5ADoALQA6AC4ALgA6ADsALgA7AC8ALwA7ADwALwA8ADAAMAA8AD0AMAA9ADEAMQA9AD4AMQA+ADIAMgA+AD8AMgA/ADMAMwA/AEAANABBADUANQBBAEIANQBCADYANgBCAEMANgBDADcANwBDAEQANwBEADgAOABEAEUAOABFADkAOQBFAEYAOQBGADoAOgBGAEcAOgBHADsAOwBHAEgAOwBIADwAPABIAEkAPABJAD0APQBJAEoAPQBKAD4APgBKAEsAPgBLAD8APwBLAEwAPwBMAEAAQABMAE0AQQBOAEIAQgBOAE8AQgBPAEMAQwBPAFAAQwBQAEQARABQAFEARABRAEUARQBRAFIARQBSAEYARgBSAFMARgBTAEcARwBTAFQARwBUAEgASABUAFUASABVAEkASQBVAFYASQBWAEoASgBWAFcASgBXAEsASwBXAFgASwBYAEwATABYAFkATABZAE0ATQBZAFoATgBbAE8ATwBbAFwATwBcAFAAUABcAF0AUABdAFEAUQBdAF4AUQBeAFIAUgBeAF8AUgBfAFMAUwBfAGAAUwBgAFQAVABgAGEAVABhAFUAVQBhAGIAVQBiAFYAVgBiAGMAVgBjAFcAVwBjAGQAVwBkAFgAWABkAGUAWABlAFkAWQBlAGYAWQBmAFoAWgBmAGcAWwBoAFwAXABoAGkAXABpAF0AXQBpAGoAXQBqAF4AXgBqAGsAXgBrAF8AXwBrAGwAXwBsAGAAYABsAG0AYABtAGEAYQBtAG4AYQBuAGIAYgBuAG8AYgBvAGMAYwBvAHAAYwBwAGQAZABwAHEAZABxAGUAZQBxAHIAZQByAGYAZgByAHMAZgBzAGcAZwBzAHQAaAB1AGkAaQB1AHYAaQB2AGoAagB2AHcAagB3AGsAawB3AHgAawB4AGwAbAB4AHkAbAB5AG0AbQB5AHoAbQB6AG4AbgB6AHsAbgB7AG8AbwB7AHwAbwB8AHAAcAB8AH0AcAB9AHEAcQB9AH4AcQB+AHIAcgB+AH8AcgB/AHMAcwB/AIAAcwCAAHQAdACAAIEAdQCCAHYAdgCCAIMAdgCDAHcAdwCDAIQAdwCEAHgAeACEAIUAeACFAHkAeQCFAIYAeQCGAHoAegCGAIcAegCHAHsAewCHAIgAewCIAHwAfACIAIkAfACJAH0AfQCJAIoAfQCKAH4AfgCKAIsAfgCLAH8AfwCLAIwAfwCMAIAAgACMAI0AgACNAIEAgQCNAI4AggCPAIMAgwCPAJAAgwCQAIQAhACQAJEAhACRAIUAhQCRAJIAhQCSAIYAhgCSAJMAhgCTAIcAhwCTAJQAhwCUAIgAiACUAJUAiACVAIkAiQCVAJYAiQCWAIoAigCWAJcAigCXAIsAiwCXAJgAiwCYAIwAjACYAJkAjACZAI0AjQCZAJoAjQCaAI4AjgCaAJsAjwCcAJAAkACcAJ0AkACdAJEAkQCdAJ4AkQCeAJIAkgCeAJ8AkgCfAJMAkwCfAKAAkwCgAJQAlACgAKEAlAChAJUAlQChAKIAlQCiAJYAlgCiAKMAlgCjAJcAlwCjAKQAlwCkAJgAmACkAKUAmAClAJkAmQClAKYAmQCmAJoAmgCmAKcAmgCnAJsAmwCnAKgAnACpAJ0AnQCpAKoAnQCqAJ4AngCqAKsAngCrAJ8AnwCrAKwAnwCsAKAAoACsAK0AoACtAKEAoQCtAK4AoQCuAKIAogCuAK8AogCvAKMAowCvALAAowCwAKQApACwALEApACxAKUApQCxALIApQCyAKYApgCyALMApgCzAKcApwCzALQApwC0AKgAqAC0ALUAqQC2AKoAqgC2ALcAqgC3AKsAqwC3ALgAqwC4AKwArAC4ALkArAC5AK0ArQC5ALoArQC6AK4ArgC6ALsArgC7AK8ArwC7ALwArwC8ALAAsAC8AL0AsAC9ALEAsQC9AL4AsQC+ALIAsgC+AL8AsgC/ALMAswC/AMAAswDAALQAtADAAMEAtADBALUAtQDBAMIAwwABAAAAwwACAAEAwwADAAIAwwAEAAMAwwAFAAQAwwAGAAUAwwAHAAYAwwAIAAcAwwAJAAgAwwAKAAkAwwALAAoAwwAMAAsAxADFANEAxQDSANEAxQDGANIAxgDTANIAxgDHANMAxwDUANMAxwDIANQAyADVANQAyADJANUAyQDWANUAyQDKANYAygDXANYAygDLANcAywDYANcAywDMANgAzADZANgAzADNANkAzQDaANkAzQDOANoAzgDbANoAzgDPANsAzwDcANsAzwDQANwA0ADdANwA0QDSAN4A0gDfAN4A0gDTAN8A0wDgAN8A0wDUAOAA1ADhAOAA1ADVAOEA1QDiAOEA1QDWAOIA1gDjAOIA1gDXAOMA1wDkAOMA1wDYAOQA2ADlAOQA2ADZAOUA2QDmAOUA2QDaAOYA2gDnAOYA2gDbAOcA2wDoAOcA2wDcAOgA3ADpAOgA3ADdAOkA3QDqAOkA3gDfAOsA3wDsAOsA3wDgAOwA4ADtAOwA4ADhAO0A4QDuAO0A4QDiAO4A4gDvAO4A4gDjAO8A4wDwAO8A4wDkAPAA5ADxAPAA5ADlAPEA5QDyAPEA5QDmAPIA5gDzAPIA5gDnAPMA5wD0APMA5wDoAPQA6AD1APQA6ADpAPUA6QD2APUA6QDqAPYA6gD3APYA6wDsAPgA7AD5APgA7ADtAPkA7QD6APkA7QDuAPoA7gD7APoA7gDvAPsA7wD8APsA7wDwAPwA8AD9APwA8ADxAP0A8QD+AP0A8QDyAP4A8gD/AP4A8gDzAP8A8wAAAf8A8wD0AAAB9AABAQAB9AD1AAEB9QACAQEB9QD2AAIB9gADAQIB9gD3AAMB9wAEAQMB+AD5AAUB+QAGAQUB+QD6AAYB+gAHAQYB+gD7AAcB+wAIAQcB+wD8AAgB/AAJAQgB/AD9AAkB/QAKAQkB/QD+AAoB/gALAQoB/gD/AAsB/wAMAQsB/wAAAQwBAAENAQwBAAEBAQ0BAQEOAQ0BAQECAQ4BAgEPAQ4BAgEDAQ8BAwEQAQ8BAwEEARABBAERARABBQEGARIBBgETARIBBgEHARMBBwEUARMBBwEIARQBCAEVARQBCAEJARUBCQEWARUBCQEKARYBCgEXARYBCgELARcBCwEYARcBCwEMARgBDAEZARgBDAENARkBDQEaARkBDQEOARoBDgEbARoBDgEPARsBDwEcARsBDwEQARwBEAEdARwBEAERAR0BEQEeAR0BEgETAR8BEwEgAR8BEwEUASABFAEhASABFAEVASEBFQEiASEBFQEWASIBFgEjASIBFgEXASMBFwEkASMBFwEYASQBGAElASQBGAEZASUBGQEmASUBGQEaASYBGgEnASYBGgEbAScBGwEoAScBGwEcASgBHAEpASgBHAEdASkBHQEqASkBHQEeASoBHgErASoBHwEgASwBIAEtASwBIAEhAS0BIQEuAS0BIQEiAS4BIgEvAS4BIgEjAS8BIwEwAS8BIwEkATABJAExATABJAElATEBJQEyATEBJQEmATIBJgEzATIBJgEnATMBJwE0ATMBJwEoATQBKAE1ATQBKAEpATUBKQE2ATUBKQEqATYBKgE3ATYBKgErATcBKwE4ATcBLAEtATkBLQE6ATkBLQEuAToBLgE7AToBLgEvATsBLwE8ATsBLwEwATwBMAE9ATwBMAExAT0BMQE+AT0BMQEyAT4BMgE/AT4BMgEzAT8BMwFAAT8BMwE0AUABNAFBAUABNAE1AUEBNQFCAUEBNQE2AUIBNgFDAUIBNgE3AUMBNwFEAUMBNwE4AUQBOAFFAUQBOQE6AUYBOgFHAUYBOgE7AUcBOwFIAUcBOwE8AUgBPAFJAUgBPAE9AUkBPQFKAUkBPQE+AUoBPgFLAUoBPgE/AUsBPwFMAUsBPwFAAUwBQAFNAUwBQAFBAU0BQQFOAU0BQQFCAU4BQgFPAU4BQgFDAU8BQwFQAU8BQwFEAVABRAFRAVABRAFFAVEBRQFSAVEBRgFHAVMBRwFUAVMBRwFIAVQBSAFVAVQBSAFJAVUBSQFWAVUBSQFKAVYBSgFXAVYBSgFLAVcBSwFYAVcBSwFMAVgBTAFZAVgBTAFNAVkBTQFaAVkBTQFOAVoBTgFbAVoBTgFPAVsBTwFcAVsBTwFQAVwBUAFdAVwBUAFRAV0BUQFeAV0BUQFSAV4BUgFfAV4BUwFUAWABVAFhAWABVAFVAWEBVQFiAWEBVQFWAWIBVgFjAWIBVgFXAWMBVwFkAWMBVwFYAWQBWAFlAWQBWAFZAWUBWQFmAWUBWQFaAWYBWgFnAWYBWgFbAWcBWwFoAWcBWwFcAWgBXAFpAWgBXAFdAWkBXQFqAWkBXQFeAWoBXgFrAWoBXgFfAWsBXwFsAWsBYAFhAW0BYQFuAW0BYQFiAW4BYgFvAW4BYgFjAW8BYwFwAW8BYwFkAXABZAFxAXABZAFlAXEBZQFyAXEBZQFmAXIBZgFzAXIBZgFnAXMBZwF0AXMBZwFoAXQBaAF1AXQBaAFpAXUBaQF2AXUBaQFqAXYBagF3AXYBagFrAXcBawF4AXcBawFsAXgBbAF5AXgBbQFuAXoBbgF7AXoBbgFvAXsBbwF8AXsBbwFwAXwBcAF9AXwBcAFxAX0BcQF+AX0BcQFyAX4BcgF/AX4BcgFzAX8BcwGAAX8BcwF0AYABdAGBAYABdAF1AYEBdQGCAYEBdQF2AYIBdgGDAYIBdgF3AYMBdwGEAYMBdwF4AYQBeAGFAYQBeAF5AYUBeQGGAYUBhwHEAMUAhwHFAMYAhwHGAMcAhwHHAMgAhwHIAMkAhwHJAMoAhwHKAMsAhwHLAMwAhwHMAM0AhwHNAM4AhwHOAM8AhwHPANAA",
44
+ "tint": "none",
45
+ "depth": "front",
46
+ "color": "#efe6c6"
47
+ },
48
+ {
49
+ "id": "ears",
50
+ "anchor": "eyes",
51
+ "layer": "back",
52
+ "svg": "<path d=\"M42 -12L86 -28L48 12Z\" fill=\"{body}\" stroke=\"{line}\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/><path d=\"M52 -7L76 -20L53 4Z\" fill=\"{bodyDark}\" opacity=\".75\"/><g transform=\"scale(-1 1)\"><path d=\"M42 -12L86 -28L48 12Z\" fill=\"{body}\" stroke=\"{line}\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/><path d=\"M52 -7L76 -20L53 4Z\" fill=\"{bodyDark}\" opacity=\".75\"/></g>",
53
+ "view": [
54
+ -90,
55
+ -32,
56
+ 180,
57
+ 48
58
+ ],
59
+ "glb": "data:model/gltf-binary;base64,Z2xURgIAAACoOAAAfAMAAEpTT057ImFzc2V0Ijp7InZlcnNpb24iOiIyLjAiLCJnZW5lcmF0b3IiOiI3b3RzIG1vZC1nbGIifSwic2NlbmUiOjAsInNjZW5lcyI6W3sibm9kZXMiOlswXX1dLCJub2RlcyI6W3sibWVzaCI6MH1dLCJtZXNoZXMiOlt7InByaW1pdGl2ZXMiOlt7ImF0dHJpYnV0ZXMiOnsiUE9TSVRJT04iOjAsIk5PUk1BTCI6MX0sImluZGljZXMiOjIsIm1hdGVyaWFsIjowfV19XSwibWF0ZXJpYWxzIjpbeyJwYnJNZXRhbGxpY1JvdWdobmVzcyI6eyJiYXNlQ29sb3JGYWN0b3IiOlsxLDEsMSwxXSwibWV0YWxsaWNGYWN0b3IiOjAsInJvdWdobmVzc0ZhY3RvciI6MC44fX1dLCJidWZmZXJzIjpbeyJieXRlTGVuZ3RoIjoxMzU4NH1dLCJidWZmZXJWaWV3cyI6W3siYnVmZmVyIjowLCJieXRlT2Zmc2V0IjowLCJieXRlTGVuZ3RoIjo0NzA0LCJ0YXJnZXQiOjM0OTYyfSx7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6NDcwNCwiYnl0ZUxlbmd0aCI6NDcwNCwidGFyZ2V0IjozNDk2Mn0seyJidWZmZXIiOjAsImJ5dGVPZmZzZXQiOjk0MDgsImJ5dGVMZW5ndGgiOjQxNzYsInRhcmdldCI6MzQ5NjN9XSwiYWNjZXNzb3JzIjpbeyJidWZmZXJWaWV3IjowLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MzkyLCJ0eXBlIjoiVkVDMyIsIm1pbiI6Wy04Ni4zODM0MjkzMTk1MjU5NywtMTMuMzc1NjM5MDc0MzMwMTM4LC00LjU2MDAwMDAwMDAwMDAwMDVdLCJtYXgiOls4Ni4zODM0MjkzMTk1MjU5NywyNi40NjE0OTk2ODI0Nzg1OTYsNC41NjAwMDAwMDAwMDAwMDA1XX0seyJidWZmZXJWaWV3IjoxLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MzkyLCJ0eXBlIjoiVkVDMyJ9LHsiYnVmZmVyVmlldyI6MiwiY29tcG9uZW50VHlwZSI6NTEyMywiY291bnQiOjIwODgsInR5cGUiOiJTQ0FMQVIifV19EDUAAEJJTgACSCdCngJWwQAAAADmOyVCH6A9wYXrEUABpB9CngL2wKm9fEAAABhCAAAAwIXrkUD/WxBCPAVsQKm9fEAaxApCP0D7QIXrEUD+twhCngIWQYX1ICYaxApCP0D7QIXrEcD/WxBCPAVsQKm9fMAAABhCAAAAwIXrkcABpB9CngL2wKm9fMDmOyVCH6A9wYXrEcACSCdCngJWwYX1oKZe3TRCIPIwwQAAAAC/1TJCGPIawSUHBUAdSi1CNa69wD5pZkDbtiVCTsFLvyUHhUCaIx5C4b2KQD5pZkD4lxhC73kBQSUHBUBZkBZC9nkXQf+8Eib4lxhC73kBQSUHBcCaIx5C4b2KQD5pZsDbtiVCTsFLvyUHhcAdSi1CNa69wD5pZsC/1TJCGPIawSUHBcBe3TRCIPIwwf+8kqb7d0JCUiQMwQAAAAAldEBCQXXwwJy58z/Z8jpC76aDwHMSU0C3bTNCNNYHP5y5c0CV6CtCfJylQHMSU0BJZyZCZzUJQZy58z9yYyRCGR8dQRFsBiZJZyZCZzUJQZy587+V6CtCfJylQHMSU8C3bTNCNNYHP5y5c8DZ8jpC76aDwHMSU8AldEBCQXXwwJy587/7d0JCUiQMwRFshqae5U9Cfp7MwAAAAACX601C7beowA2J3j8YhUhCrUUNwKW4QECSJEFCRGP9Pw2JXkANxDlCeFTFQKW4QECOXTRCyLQTQQ2J3j+HYzJCEKglQX949SWOXTRCyLQTQQ2J3r8NxDlCeFTFQKW4QMCSJEFCRGP9Pw2JXsAYhUhCrUUNwKW4QMCX601C7beowA2J3r+e5U9Cfp7MwH94daYcJV1CK858wAAAAAATO1tC83w8wOYNyj9FAFZCAVJMvvL7LkBu205C60NjQOYNSkCXtkdCe6bpQPL7LkDJe0JCMsEgQeYNyj+/kUBCgNUwQfjg3iXJe0JCMsEgQeYNyr+XtkdCe6bpQPL7LsBu205C60NjQOYNSsBFAFZCAVJMvvL7LsATO1tC83w8wOYNyr8cJV1CK858wPjgXqbJN2pCpLe1vwAAAAC/Y2hCWNsGv6MVtj8JZWNCltD0P5iwHUBJklxCNNanQKMVNkCJv1VCITwJQZiwHUDTwFBC6kMwQaMVtj/J7E5CKY0+QdHZyCXTwFBC6kMwQaMVtr+Jv1VCITwJQZiwHcBJklxCNNanQKMVNsAJZWNCltD0P5iwHcC/Y2hCWNsGv6MVtr/JN2pCpLe1v9HZSKbFH3dC9qCZPwAAAAByZ3VCNyP+P1KDoj91tHBC5S6EQIi9DEAlSWpCjfXhQFKDIkDV3WNCGt4fQYi9DEDXKl9CJjFCQVKDoj+Ecl1CbsFOQR1DsyXXKl9CJjFCQVKDor/V3WNCGt4fQYi9DMAlSWpCjfXhQFKDIsB1tHBC5S6EQIi9DMByZ3VCNyP+P1KDor/FH3dC9qCZPx1DM6a+74FCNFd6QAAAAAAjJIFCQPySQDhEjz++731CzZXOQBMl+D8AAHhCAAAQQThED0BCEHJCGrU4QRMl+D+6t21C4IFWQThEjz+DIGxCM2phQTUIniW6t21C4IFWQThEj79CEHJCGrU4QRMl+L8AAHhCAAAQQThED8C+731CzZXOQBMl+L8jJIFCQPySQDhEj7++74FCNFd6QDUIHqbAPIhCIefWQAAAAAA5hIdCaojpQIGWeD8XjIVCKzcOQZBI1z9u24JCx/owQYGW+D/FKoBCYr5TQZBI1z9FZXxCWDFtQYGWeD849HpC/YF2QaoaiSVFZXxCWDFtQYGWeL/FKoBCYr5TQZBI179u24JCx/owQYGW+L8XjIVCKzcOQZBI1785hIdCaojpQIGWeL/APIhCIefWQKoaCaYveI5Ck9EZQQAAAAAY1Y1CP5ohQZMdUz+FF4xCVt42QdfUtj/btolCGutTQZMd0z8yVodC3vdwQdfUtj+fmIVC+h2DQZMdUz+I9YRCUQKHQbffaCWfmIVC+h2DQZMdU78yVodC3vdwQdfUtr/btolCGutTQZMd07+FF4xCVt42QdfUtr8Y1Y1CP5ohQZMdU78veI5Ck9EZQbff6KVSo5RCWbNJQQAAAADZF5RCTQNQQd8OLj/NmpJCKkJhQR29lj9JkpBC+9B4Qd8Orj/FiY5C5i+IQR29lj+5DI1CVM+QQd8OLj9BgYxCTveTQUb/PyW5DI1CVM+QQd8OLr/FiY5C5i+IQR29lr9JkpBC+9B4Qd8Orr/NmpJCKkJhQR29lr/ZF5RCTQNQQd8OLr9So5RCWbNJQUb/v6Viv5pCIhx7QQAAAACMTZpCFgGAQbleCT+MFplCI7KGQY7ubT+3bZdCNNaPQbleiT/hxJVCRvqYQY7ubT/hjZRCU6ufQbleCT8MHJRCWB6iQSSHFyXhjZRCU6ufQbleCb/hxJVCRvqYQY7ubb+3bZdCNNaPQbleib+MFplCI7KGQY7ubb+MTZpCFgGAQbleCb9iv5pCIhx7QSSHl6WOzaBCiAeXQQAAAAA4d6BCysyYQWUHyj5Zi59CHaOdQVD2Lj8lSZ5Csj6kQWUHSj/wBp1CRtqqQVD2Lj8RG5xCmbCvQWUHyj67xJtC23WxQcvZ3iQRG5xCmbCvQWUHyr7wBp1CRtqqQVD2Lr8lSZ5Csj6kQWUHSr9Zi59CHaOdQVD2Lr84d6BCysyYQWUHyr6OzaBCiAeXQcvZXqX2zqZCpkexQQAAAADWlaZCIGayQQHsgT7E+aVCznS1QQQI4T6SJKVC9qG5QQHsAT9gT6RCHc+9QQQI4T5Ps6NCy93AQQHsgT4veqNCRfzBQehPjyRPs6NCy93AQQHsgb5gT6RCHc+9QQQI4b6SJKVC9qG5QQHsAb/E+aVCznS1QQQI4b7WlaZCIGayQQHsgb72zqZCpkexQehPD6VRxKxC2U7MQQAAAAAEqqxCes3MQdV46T0oYqxCbSfOQVQxSj4AAKxCAADQQdV4aT7YnatCk9jRQVQxSj78VatChjLTQdV46T2vO6tCJ7HTQWvEACT8VatChjLTQdV46b3YnatCk9jRQVQxSr4AAKxCAADQQdV4ab4oYqxCbSfOQVQxSr4EqqxCes3MQdV46b1RxKxC2U7MQWvEgKQAABhCAAAAwAAAAAACSCfCngJWwQAAAADmOyXCH6A9wYXrEUABpB/CngL2wKm9fEAAABjCAAAAwIXrkUD/WxDCPAVsQKm9fEAaxArCP0D7QIXrEUD+twjCngIWQYX1ICYaxArCP0D7QIXrEcD/WxDCPAVsQKm9fMAAABjCAAAAwIXrkcABpB/CngL2wKm9fMDmOyXCH6A9wYXrEcACSCfCngJWwYX1oKZe3TTCIPIwwQAAAAC/1TLCGPIawSUHBUAdSi3CNa69wD5pZkDbtiXCTsFLvyUHhUCaIx7C4b2KQD5pZkD4lxjC73kBQSUHBUBZkBbC9nkXQf+8Eib4lxjC73kBQSUHBcCaIx7C4b2KQD5pZsDbtiXCTsFLvyUHhcAdSi3CNa69wD5pZsC/1TLCGPIawSUHBcBe3TTCIPIwwf+8kqb7d0LCUiQMwQAAAAAldEDCQXXwwJy58z/Z8jrC76aDwHMSU0C3bTPCNNYHP5y5c0CV6CvCfJylQHMSU0BJZybCZzUJQZy58z9yYyTCGR8dQRFsBiZJZybCZzUJQZy587+V6CvCfJylQHMSU8C3bTPCNNYHP5y5c8DZ8jrC76aDwHMSU8AldEDCQXXwwJy587/7d0LCUiQMwRFshqae5U/Cfp7MwAAAAACX603C7beowA2J3j8YhUjCrUUNwKW4QECSJEHCRGP9Pw2JXkANxDnCeFTFQKW4QECOXTTCyLQTQQ2J3j+HYzLCEKglQX949SWOXTTCyLQTQQ2J3r8NxDnCeFTFQKW4QMCSJEHCRGP9Pw2JXsAYhUjCrUUNwKW4QMCX603C7beowA2J3r+e5U/Cfp7MwH94daYcJV3CK858wAAAAAATO1vC83w8wOYNyj9FAFbCAVJMvvL7LkBu207C60NjQOYNSkCXtkfCe6bpQPL7LkDJe0LCMsEgQeYNyj+/kUDCgNUwQfjg3iXJe0LCMsEgQeYNyr+XtkfCe6bpQPL7LsBu207C60NjQOYNSsBFAFbCAVJMvvL7LsATO1vC83w8wOYNyr8cJV3CK858wPjgXqbJN2rCpLe1vwAAAAC/Y2jCWNsGv6MVtj8JZWPCltD0P5iwHUBJklzCNNanQKMVNkCJv1XCITwJQZiwHUDTwFDC6kMwQaMVtj/J7E7CKY0+QdHZyCXTwFDC6kMwQaMVtr+Jv1XCITwJQZiwHcBJklzCNNanQKMVNsAJZWPCltD0P5iwHcC/Y2jCWNsGv6MVtr/JN2rCpLe1v9HZSKbFH3fC9qCZPwAAAAByZ3XCNyP+P1KDoj91tHDC5S6EQIi9DEAlSWrCjfXhQFKDIkDV3WPCGt4fQYi9DEDXKl/CJjFCQVKDoj+Ecl3CbsFOQR1DsyXXKl/CJjFCQVKDor/V3WPCGt4fQYi9DMAlSWrCjfXhQFKDIsB1tHDC5S6EQIi9DMByZ3XCNyP+P1KDor/FH3fC9qCZPx1DM6a+74HCNFd6QAAAAAAjJIHCQPySQDhEjz++733CzZXOQBMl+D8AAHjCAAAQQThED0BCEHLCGrU4QRMl+D+6t23C4IFWQThEjz+DIGzCM2phQTUIniW6t23C4IFWQThEj79CEHLCGrU4QRMl+L8AAHjCAAAQQThED8C+733CzZXOQBMl+L8jJIHCQPySQDhEj7++74HCNFd6QDUIHqbAPIjCIefWQAAAAAA5hIfCaojpQIGWeD8XjIXCKzcOQZBI1z9u24LCx/owQYGW+D/FKoDCYr5TQZBI1z9FZXzCWDFtQYGWeD849HrC/YF2QaoaiSVFZXzCWDFtQYGWeL/FKoDCYr5TQZBI179u24LCx/owQYGW+L8XjIXCKzcOQZBI1785hIfCaojpQIGWeL/APIjCIefWQKoaCaYveI7Ck9EZQQAAAAAY1Y3CP5ohQZMdUz+FF4zCVt42QdfUtj/btonCGutTQZMd0z8yVofC3vdwQdfUtj+fmIXC+h2DQZMdUz+I9YTCUQKHQbffaCWfmIXC+h2DQZMdU78yVofC3vdwQdfUtr/btonCGutTQZMd07+FF4zCVt42QdfUtr8Y1Y3CP5ohQZMdU78veI7Ck9EZQbff6KVSo5TCWbNJQQAAAADZF5TCTQNQQd8OLj/NmpLCKkJhQR29lj9JkpDC+9B4Qd8Orj/FiY7C5i+IQR29lj+5DI3CVM+QQd8OLj9BgYzCTveTQUb/PyW5DI3CVM+QQd8OLr/FiY7C5i+IQR29lr9JkpDC+9B4Qd8Orr/NmpLCKkJhQR29lr/ZF5TCTQNQQd8OLr9So5TCWbNJQUb/v6Viv5rCIhx7QQAAAACMTZrCFgGAQbleCT+MFpnCI7KGQY7ubT+3bZfCNNaPQbleiT/hxJXCRvqYQY7ubT/hjZTCU6ufQbleCT8MHJTCWB6iQSSHFyXhjZTCU6ufQbleCb/hxJXCRvqYQY7ubb+3bZfCNNaPQbleib+MFpnCI7KGQY7ubb+MTZrCFgGAQbleCb9iv5rCIhx7QSSHl6WOzaDCiAeXQQAAAAA4d6DCysyYQWUHyj5Zi5/CHaOdQVD2Lj8lSZ7Csj6kQWUHSj/wBp3CRtqqQVD2Lj8RG5zCmbCvQWUHyj67xJvC23WxQcvZ3iQRG5zCmbCvQWUHyr7wBp3CRtqqQVD2Lr8lSZ7Csj6kQWUHSr9Zi5/CHaOdQVD2Lr84d6DCysyYQWUHyr6OzaDCiAeXQcvZXqX2zqbCpkexQQAAAADWlabCIGayQQHsgT7E+aXCznS1QQQI4T6SJKXC9qG5QQHsAT9gT6TCHc+9QQQI4T5Ps6PCy93AQQHsgT4veqPCRfzBQehPjyRPs6PCy93AQQHsgb5gT6TCHc+9QQQI4b6SJKXC9qG5QQHsAb/E+aXCznS1QQQI4b7WlabCIGayQQHsgb72zqbCpkexQehPD6VRxKzC2U7MQQAAAAAEqqzCes3MQdV46T0oYqzCbSfOQVQxSj4AAKzCAADQQdV4aT7YnavCk9jRQVQxSj78VavChjLTQdV46T2vO6vCJ7HTQWvEACT8VavChjLTQdV46b3YnavCk9jRQVQxSr4AAKzCAADQQdV4ab4oYqzCbSfOQVQxSr4EqqzCes3MQdV46b1RxKzC2U7MQWvEgKQAABjCAAAAwAAAAAASAKM+KK5yvwAAAAC3OjM+3msFv73WVT+vuIs9oAVQvoMNej/UpQgjIHLLowAAgD+vuIu9oAVQPoMNej+3OjO+3msFP73WVT8SAKO+KK5yP3fHuSW3OjO+3msFP73WVb+vuIu9oAVQPoMNer+++MyjmJWYJAAAgL+vuIs9oAVQvoMNer+3OjM+3msFv73WVb8SAKM+KK5yv3fHOaYgRLE+wCpwvwAAAABj6kI+IgoEv73WVT8s85c9G95NvoMNej9zmxQjvVbJowAAgD8s85e9G95NPoMNej9j6kK+IgoEP73WVT8gRLG+wCpwP3fHuSVj6kK+IgoEP73WVb8s85e9G95NPoMNer8t6d6jDgGXJAAAgL8s85c9G95NvoMNer9j6kI+IgoEv73WVb8gRLE+wCpwv3fHOaZEGcA+Y0xtvwAAAACXOVM+ZXYCv73WVT8HqqQ9n2hLvoMNej+0CiEjGu/GowAAgD8HqqS9n2hLPoMNej+XOVO+ZXYCP73WVT9EGcC+Y0xtP3fHuSWXOVO+ZXYCP73WVb8HqqS9n2hLPoMNer8NkPGjVDOVJAAAgL8HqqQ9n2hLvoMNer+XOVM+ZXYCv73WVb9EGcA+Y0xtv3fHOaabY84+jkdqvwAAAAAs8GI+gc0Av73WVT/n6bA9KdJIvoMNej+YBS0jN2fEowAAgD/n6bC9KdJIPoMNej8s8GK+gc0AP73WVT+bY86+jkdqP3fHuSUs8GK+gc0AP73WVb/n6bC9KdJIPoMNer8yxAGkaU2TJAAAgL/n6bA9KdJIvoMNer8s8GI+gc0Av73WVb+bY84+jkdqv3fHOaZBINw+3iFnvwAAAAD0CnI+GiX+vr3WVT9SsLw9ih9GvoMNej+ziTgjycPBowAAgD9SsLy9ih9GPoMNej/0CnK+GiX+Pr3WVT9BINy+3iFnP3fHuSX0CnK+GiX+Pr3WVb9SsLy9ih9GPoMNer9GZwqk11KRJAAAgL9SsLw9ih9GvoMNer/0CnI+GiX+vr3WVb9BINw+3iFnv3fHOaa5Tek+weBjvwAAAAAnRIA+CpH6vr3WVT8D/Mc9aFVDvoMNej/GlUMjXAm/owAAgD8D/Me9aFVDPoMNej8nRIC+CpH6Pr3WVT+5Tem+weBjP3fHuSUnRIC+CpH6Pr3WVb8D/Me9aFVDPoMNer9VsBKkBUePJAAAgL8D/Mc9aFVDvoMNer8nRIA+CpH6vr3WVb+5Tek+weBjv3fHOabM6/U+YolgvwAAAAD9M4c+geT2vr3WVT/IzNI9MXhAvoMNej+jKU4jRzy8owAAgD/IzNK9MXhAPoMNej/9M4e+geT2Pr3WVT/M6/W+YolgP3fHuSX9M4e+geT2Pr3WVb/IzNK9MXhAPoMNer86nxqkNS2NJAAAgL/IzNI9MXhAvoMNer/9M4c+geT2vr3WVb/M6/U+Yolgv3fHOaax/QA/nSBdvwAAAAB91Y0+1yTzvr3WVT9qI909Eow9voMNej8MRlgjnWC5owAAgD9qI929Eow9PoMNej991Y2+1yTzPr3WVT+x/QC/nSBdP3fHuSV91Y2+1yTzPr3WVb9qI929Eow9PoMNer+JNCKkdgiLJAAAgL9qI909Eow9voMNer991Y0+1yTzvr3WVb+x/QA/nSBdv3fHOaYtvwY/+qpZvwAAAACuKZQ+Blfvvr3WVT+DAec96pQ6voMNej+T7GEjKHq2owAAgD+DAee96pQ6PoMNej+uKZS+BlfvPr3WVT8tvwa/+qpZP3fHuSWuKZS+BlfvPr3WVb+DAee96pQ6PoMNer9vcSmkntuIJAAAgL+DAec96pQ6voMNer+uKZQ+Blfvvr3WVb8tvwY/+qpZv3fHOaazOww/oixWvwAAAAAMMpo+on/rvr3WVT9jafA9TJY3voMNej97H2sjZ4yzowAAgD9jafC9TJY3PoMNej8MMpq+on/rPr3WVT+zOwy/oixWP3fHuSUMMpq+on/rPr3WVb9jafC9TJY3PoMNer+cVzCkTamGJAAAgL9jafA9TJY3voMNer8MMpo+on/rvr3WVb+zOww/oixWv3fHOabydBE/X6lSvwAAAABv8J8+1qLnvr3WVT/tXfk9dpM0voMNej+W4XMjh5qwowAAgD/tXfm9dpM0PoMNej9v8J++1qLnPr3WVT/ydBG/X6lSP3fHuSVv8J++1qLnPr3WVb/tXfm9dpM0PoMNer8w6Tak5XOEJAAAgL/tXfk9dpM0voMNer9v8J8+1qLnvr3WVb/ydBE/X6lSv3fHOabgbBY/nSRPvwAAAAAAZ6U+ZcTjvr3WVT898QA+WY8xvoMNej8sNnwjZqetowAAgD898QC+WY8xPoMNej8AZ6W+ZcTjPr3WVT/gbBa/nSRPP3fHuSUAZ6W+ZcTjPr3WVb898QC+WY8xPoMNer+hKD2kjD2CJAAAgL898QA+WY8xvoMNer8AZ6U+ZcTjvr3WVb/gbBY/nSRPv3fHOaamJRs/aqFLvwAAAAAhmKo+q+ffvr3WVT9i/QQ+kYwuvoMNej9vEIIjkrWqowAAgD9i/QS+kYwuPoMNej8hmKq+q+ffPr3WVT+mJRu/aqFLP3fHuSUhmKq+q+ffPr3WVb9i/QS+kYwuPoMNer+nGEOkLgiAJAAAgL9i/QQ+kYwuvoMNer8hmKo+q+ffvr3WVb+mJRs/aqFLv3fHOaaXoR8/eCJIvwAAAABdhq8+ng/cvr3WVT9h1Qg+b40rvoMNej/J0oUjUcenowAAgD9h1Qi+b40rPoMNej9dhq++ng/cPr3WVT+XoR+/eCJIP3fHuSVdhq++ng/cPr3WVb9h1Qi+b40rPoMNer8uvEik+ap7JAAAgL9h1Qg+b40rvoMNer9dhq8+ng/cvr3WVb+XoR8/eCJIv3fHOaa1mCM/EuhEvwAAAACN4rM+8YLYvr3WVT+GOww+DskovoMNej/KJYkjhhKlowAAgD+GOwy+DskoPoMNej+N4rO+8YLYPr3WVT+1mCO/EuhEP3fHuSWN4rO+8YLYPr3WVb+GOwy+DskoPoMNer+vuE2kyJt3JAAAgL+GOww+DskovoMNer+N4rM+8YLYvr3WVb+1mCM/EuhEv3fHOabp3HK/m+ihvgAAAAASAKO+KK5yvwAAAAC3OjO+3msFv73WVT+vuIu9oAVQvoMNej/UpQijIHLLowAAgD+vuIs9oAVQPoMNej+3OjM+3msFP73WVT8SAKM+KK5yP3fHuSW3OjM+3msFP73WVb+vuIs9oAVQPoMNer+++MwjmJWYJAAAgL+vuIu9oAVQvoMNer+3OjO+3msFv73WVb8SAKO+KK5yv3fHOaYgRLG+wCpwvwAAAABj6kK+IgoEv73WVT8s85e9G95NvoMNej9zmxSjvVbJowAAgD8s85c9G95NPoMNej9j6kI+IgoEP73WVT8gRLE+wCpwP3fHuSVj6kI+IgoEP73WVb8s85c9G95NPoMNer8t6d4jDgGXJAAAgL8s85e9G95NvoMNer9j6kK+IgoEv73WVb8gRLG+wCpwv3fHOaZEGcC+Y0xtvwAAAACXOVO+ZXYCv73WVT8HqqS9n2hLvoMNej+0CiGjGu/GowAAgD8HqqQ9n2hLPoMNej+XOVM+ZXYCP73WVT9EGcA+Y0xtP3fHuSWXOVM+ZXYCP73WVb8HqqQ9n2hLPoMNer8NkPEjVDOVJAAAgL8HqqS9n2hLvoMNer+XOVO+ZXYCv73WVb9EGcC+Y0xtv3fHOaabY86+jkdqvwAAAAAs8GK+gc0Av73WVT/n6bC9KdJIvoMNej+YBS2jN2fEowAAgD/n6bA9KdJIPoMNej8s8GI+gc0AP73WVT+bY84+jkdqP3fHuSUs8GI+gc0AP73WVb/n6bA9KdJIPoMNer8yxAEkaU2TJAAAgL/n6bC9KdJIvoMNer8s8GK+gc0Av73WVb+bY86+jkdqv3fHOaZBINy+3iFnvwAAAAD0CnK+GiX+vr3WVT9SsLy9ih9GvoMNej+ziTijycPBowAAgD9SsLw9ih9GPoMNej/0CnI+GiX+Pr3WVT9BINw+3iFnP3fHuSX0CnI+GiX+Pr3WVb9SsLw9ih9GPoMNer9GZwok11KRJAAAgL9SsLy9ih9GvoMNer/0CnK+GiX+vr3WVb9BINy+3iFnv3fHOaa5Tem+weBjvwAAAAAnRIC+CpH6vr3WVT8D/Me9aFVDvoMNej/GlUOjXAm/owAAgD8D/Mc9aFVDPoMNej8nRIA+CpH6Pr3WVT+5Tek+weBjP3fHuSUnRIA+CpH6Pr3WVb8D/Mc9aFVDPoMNer9VsBIkBUePJAAAgL8D/Me9aFVDvoMNer8nRIC+CpH6vr3WVb+5Tem+weBjv3fHOabM6/W+YolgvwAAAAD9M4e+geT2vr3WVT/IzNK9MXhAvoMNej+jKU6jRzy8owAAgD/IzNI9MXhAPoMNej/9M4c+geT2Pr3WVT/M6/U+YolgP3fHuSX9M4c+geT2Pr3WVb/IzNI9MXhAPoMNer86nxokNS2NJAAAgL/IzNK9MXhAvoMNer/9M4e+geT2vr3WVb/M6/W+Yolgv3fHOaax/QC/nSBdvwAAAAB91Y2+1yTzvr3WVT9qI929Eow9voMNej8MRlijnWC5owAAgD9qI909Eow9PoMNej991Y0+1yTzPr3WVT+x/QA/nSBdP3fHuSV91Y0+1yTzPr3WVb9qI909Eow9PoMNer+JNCIkdgiLJAAAgL9qI929Eow9voMNer991Y2+1yTzvr3WVb+x/QC/nSBdv3fHOaYtvwa/+qpZvwAAAACuKZS+Blfvvr3WVT+DAee96pQ6voMNej+T7GGjKHq2owAAgD+DAec96pQ6PoMNej+uKZQ+BlfvPr3WVT8tvwY/+qpZP3fHuSWuKZQ+BlfvPr3WVb+DAec96pQ6PoMNer9vcSkkntuIJAAAgL+DAee96pQ6voMNer+uKZS+Blfvvr3WVb8tvwa/+qpZv3fHOaazOwy/oixWvwAAAAAMMpq+on/rvr3WVT9jafC9TJY3voMNej97H2ujZ4yzowAAgD9jafA9TJY3PoMNej8MMpo+on/rPr3WVT+zOww/oixWP3fHuSUMMpo+on/rPr3WVb9jafA9TJY3PoMNer+cVzAkTamGJAAAgL9jafC9TJY3voMNer8MMpq+on/rvr3WVb+zOwy/oixWv3fHOabydBG/X6lSvwAAAABv8J++1qLnvr3WVT/tXfm9dpM0voMNej+W4XOjh5qwowAAgD/tXfk9dpM0PoMNej9v8J8+1qLnPr3WVT/ydBE/X6lSP3fHuSVv8J8+1qLnPr3WVb/tXfk9dpM0PoMNer8w6TYk5XOEJAAAgL/tXfm9dpM0voMNer9v8J++1qLnvr3WVb/ydBG/X6lSv3fHOabgbBa/nSRPvwAAAAAAZ6W+ZcTjvr3WVT898QC+WY8xvoMNej8sNnyjZqetowAAgD898QA+WY8xPoMNej8AZ6U+ZcTjPr3WVT/gbBY/nSRPP3fHuSUAZ6U+ZcTjPr3WVb898QA+WY8xPoMNer+hKD0kjD2CJAAAgL898QC+WY8xvoMNer8AZ6W+ZcTjvr3WVb/gbBa/nSRPv3fHOaamJRu/aqFLvwAAAAAhmKq+q+ffvr3WVT9i/QS+kYwuvoMNej9vEIKjkrWqowAAgD9i/QQ+kYwuPoMNej8hmKo+q+ffPr3WVT+mJRs/aqFLP3fHuSUhmKo+q+ffPr3WVb9i/QQ+kYwuPoMNer+nGEMkLgiAJAAAgL9i/QS+kYwuvoMNer8hmKq+q+ffvr3WVb+mJRu/aqFLv3fHOaaXoR+/eCJIvwAAAABdhq++ng/cvr3WVT9h1Qi+b40rvoMNej/J0oWjUcenowAAgD9h1Qg+b40rPoMNej9dhq8+ng/cPr3WVT+XoR8/eCJIP3fHuSVdhq8+ng/cPr3WVb9h1Qg+b40rPoMNer8uvEgk+ap7JAAAgL9h1Qi+b40rvoMNer9dhq++ng/cvr3WVb+XoR+/eCJIv3fHOaa1mCO/EuhEvwAAAACN4rO+8YLYvr3WVT+GOwy+DskovoMNej/KJYmjhhKlowAAgD+GOww+DskoPoMNej+N4rM+8YLYPr3WVT+1mCM/EuhEP3fHuSWN4rM+8YLYPr3WVb+GOww+DskoPoMNer+vuE0kyJt3JAAAgL+GOwy+DskovoMNer+N4rO+8YLYvr3WVb+1mCO/EuhEv3fHOabp3HI/m+ihvgAAAAAAAA0AAQABAA0ADgABAA4AAgACAA4ADwACAA8AAwADAA8AEAADABAABAAEABAAEQAEABEABQAFABEAEgAFABIABgAGABIAEwAGABMABwAHABMAFAAHABQACAAIABQAFQAIABUACQAJABUAFgAJABYACgAKABYAFwAKABcACwALABcAGAALABgADAAMABgAGQANABoADgAOABoAGwAOABsADwAPABsAHAAPABwAEAAQABwAHQAQAB0AEQARAB0AHgARAB4AEgASAB4AHwASAB8AEwATAB8AIAATACAAFAAUACAAIQAUACEAFQAVACEAIgAVACIAFgAWACIAIwAWACMAFwAXACMAJAAXACQAGAAYACQAJQAYACUAGQAZACUAJgAaACcAGwAbACcAKAAbACgAHAAcACgAKQAcACkAHQAdACkAKgAdACoAHgAeACoAKwAeACsAHwAfACsALAAfACwAIAAgACwALQAgAC0AIQAhAC0ALgAhAC4AIgAiAC4ALwAiAC8AIwAjAC8AMAAjADAAJAAkADAAMQAkADEAJQAlADEAMgAlADIAJgAmADIAMwAnADQAKAAoADQANQAoADUAKQApADUANgApADYAKgAqADYANwAqADcAKwArADcAOAArADgALAAsADgAOQAsADkALQAtADkAOgAtADoALgAuADoAOwAuADsALwAvADsAPAAvADwAMAAwADwAPQAwAD0AMQAxAD0APgAxAD4AMgAyAD4APwAyAD8AMwAzAD8AQAA0AEEANQA1AEEAQgA1AEIANgA2AEIAQwA2AEMANwA3AEMARAA3AEQAOAA4AEQARQA4AEUAOQA5AEUARgA5AEYAOgA6AEYARwA6AEcAOwA7AEcASAA7AEgAPAA8AEgASQA8AEkAPQA9AEkASgA9AEoAPgA+AEoASwA+AEsAPwA/AEsATAA/AEwAQABAAEwATQBBAE4AQgBCAE4ATwBCAE8AQwBDAE8AUABDAFAARABEAFAAUQBEAFEARQBFAFEAUgBFAFIARgBGAFIAUwBGAFMARwBHAFMAVABHAFQASABIAFQAVQBIAFUASQBJAFUAVgBJAFYASgBKAFYAVwBKAFcASwBLAFcAWABLAFgATABMAFgAWQBMAFkATQBNAFkAWgBOAFsATwBPAFsAXABPAFwAUABQAFwAXQBQAF0AUQBRAF0AXgBRAF4AUgBSAF4AXwBSAF8AUwBTAF8AYABTAGAAVABUAGAAYQBUAGEAVQBVAGEAYgBVAGIAVgBWAGIAYwBWAGMAVwBXAGMAZABXAGQAWABYAGQAZQBYAGUAWQBZAGUAZgBZAGYAWgBaAGYAZwBbAGgAXABcAGgAaQBcAGkAXQBdAGkAagBdAGoAXgBeAGoAawBeAGsAXwBfAGsAbABfAGwAYABgAGwAbQBgAG0AYQBhAG0AbgBhAG4AYgBiAG4AbwBiAG8AYwBjAG8AcABjAHAAZABkAHAAcQBkAHEAZQBlAHEAcgBlAHIAZgBmAHIAcwBmAHMAZwBnAHMAdABoAHUAaQBpAHUAdgBpAHYAagBqAHYAdwBqAHcAawBrAHcAeABrAHgAbABsAHgAeQBsAHkAbQBtAHkAegBtAHoAbgBuAHoAewBuAHsAbwBvAHsAfABvAHwAcABwAHwAfQBwAH0AcQBxAH0AfgBxAH4AcgByAH4AfwByAH8AcwBzAH8AgABzAIAAdAB0AIAAgQB1AIIAdgB2AIIAgwB2AIMAdwB3AIMAhAB3AIQAeAB4AIQAhQB4AIUAeQB5AIUAhgB5AIYAegB6AIYAhwB6AIcAewB7AIcAiAB7AIgAfAB8AIgAiQB8AIkAfQB9AIkAigB9AIoAfgB+AIoAiwB+AIsAfwB/AIsAjAB/AIwAgACAAIwAjQCAAI0AgQCBAI0AjgCCAI8AgwCDAI8AkACDAJAAhACEAJAAkQCEAJEAhQCFAJEAkgCFAJIAhgCGAJIAkwCGAJMAhwCHAJMAlACHAJQAiACIAJQAlQCIAJUAiQCJAJUAlgCJAJYAigCKAJYAlwCKAJcAiwCLAJcAmACLAJgAjACMAJgAmQCMAJkAjQCNAJkAmgCNAJoAjgCOAJoAmwCPAJwAkACQAJwAnQCQAJ0AkQCRAJ0AngCRAJ4AkgCSAJ4AnwCSAJ8AkwCTAJ8AoACTAKAAlACUAKAAoQCUAKEAlQCVAKEAogCVAKIAlgCWAKIAowCWAKMAlwCXAKMApACXAKQAmACYAKQApQCYAKUAmQCZAKUApgCZAKYAmgCaAKYApwCaAKcAmwCbAKcAqACcAKkAnQCdAKkAqgCdAKoAngCeAKoAqwCeAKsAnwCfAKsArACfAKwAoACgAKwArQCgAK0AoQChAK0ArgChAK4AogCiAK4ArwCiAK8AowCjAK8AsACjALAApACkALAAsQCkALEApQClALEAsgClALIApgCmALIAswCmALMApwCnALMAtACnALQAqACoALQAtQCpALYAqgCqALYAtwCqALcAqwCrALcAuACrALgArACsALgAuQCsALkArQCtALkAugCtALoArgCuALoAuwCuALsArwCvALsAvACvALwAsACwALwAvQCwAL0AsQCxAL0AvgCxAL4AsgCyAL4AvwCyAL8AswCzAL8AwACzAMAAtAC0AMAAwQC0AMEAtQC1AMEAwgDDAAEAAADDAAIAAQDDAAMAAgDDAAQAAwDDAAUABADDAAYABQDDAAcABgDDAAgABwDDAAkACADDAAoACQDDAAsACgDDAAwACwDEAMUA0QDFANIA0QDFAMYA0gDGANMA0gDGAMcA0wDHANQA0wDHAMgA1ADIANUA1ADIAMkA1QDJANYA1QDJAMoA1gDKANcA1gDKAMsA1wDLANgA1wDLAMwA2ADMANkA2ADMAM0A2QDNANoA2QDNAM4A2gDOANsA2gDOAM8A2wDPANwA2wDPANAA3ADQAN0A3ADRANIA3gDSAN8A3gDSANMA3wDTAOAA3wDTANQA4ADUAOEA4ADUANUA4QDVAOIA4QDVANYA4gDWAOMA4gDWANcA4wDXAOQA4wDXANgA5ADYAOUA5ADYANkA5QDZAOYA5QDZANoA5gDaAOcA5gDaANsA5wDbAOgA5wDbANwA6ADcAOkA6ADcAN0A6QDdAOoA6QDeAN8A6wDfAOwA6wDfAOAA7ADgAO0A7ADgAOEA7QDhAO4A7QDhAOIA7gDiAO8A7gDiAOMA7wDjAPAA7wDjAOQA8ADkAPEA8ADkAOUA8QDlAPIA8QDlAOYA8gDmAPMA8gDmAOcA8wDnAPQA8wDnAOgA9ADoAPUA9ADoAOkA9QDpAPYA9QDpAOoA9gDqAPcA9gDrAOwA+ADsAPkA+ADsAO0A+QDtAPoA+QDtAO4A+gDuAPsA+gDuAO8A+wDvAPwA+wDvAPAA/ADwAP0A/ADwAPEA/QDxAP4A/QDxAPIA/gDyAP8A/gDyAPMA/wDzAAAB/wDzAPQAAAH0AAEBAAH0APUAAQH1AAIBAQH1APYAAgH2AAMBAgH2APcAAwH3AAQBAwH4APkABQH5AAYBBQH5APoABgH6AAcBBgH6APsABwH7AAgBBwH7APwACAH8AAkBCAH8AP0ACQH9AAoBCQH9AP4ACgH+AAsBCgH+AP8ACwH/AAwBCwH/AAABDAEAAQ0BDAEAAQEBDQEBAQ4BDQEBAQIBDgECAQ8BDgECAQMBDwEDARABDwEDAQQBEAEEAREBEAEFAQYBEgEGARMBEgEGAQcBEwEHARQBEwEHAQgBFAEIARUBFAEIAQkBFQEJARYBFQEJAQoBFgEKARcBFgEKAQsBFwELARgBFwELAQwBGAEMARkBGAEMAQ0BGQENARoBGQENAQ4BGgEOARsBGgEOAQ8BGwEPARwBGwEPARABHAEQAR0BHAEQAREBHQERAR4BHQESARMBHwETASABHwETARQBIAEUASEBIAEUARUBIQEVASIBIQEVARYBIgEWASMBIgEWARcBIwEXASQBIwEXARgBJAEYASUBJAEYARkBJQEZASYBJQEZARoBJgEaAScBJgEaARsBJwEbASgBJwEbARwBKAEcASkBKAEcAR0BKQEdASoBKQEdAR4BKgEeASsBKgEfASABLAEgAS0BLAEgASEBLQEhAS4BLQEhASIBLgEiAS8BLgEiASMBLwEjATABLwEjASQBMAEkATEBMAEkASUBMQElATIBMQElASYBMgEmATMBMgEmAScBMwEnATQBMwEnASgBNAEoATUBNAEoASkBNQEpATYBNQEpASoBNgEqATcBNgEqASsBNwErATgBNwEsAS0BOQEtAToBOQEtAS4BOgEuATsBOgEuAS8BOwEvATwBOwEvATABPAEwAT0BPAEwATEBPQExAT4BPQExATIBPgEyAT8BPgEyATMBPwEzAUABPwEzATQBQAE0AUEBQAE0ATUBQQE1AUIBQQE1ATYBQgE2AUMBQgE2ATcBQwE3AUQBQwE3ATgBRAE4AUUBRAE5AToBRgE6AUcBRgE6ATsBRwE7AUgBRwE7ATwBSAE8AUkBSAE8AT0BSQE9AUoBSQE9AT4BSgE+AUsBSgE+AT8BSwE/AUwBSwE/AUABTAFAAU0BTAFAAUEBTQFBAU4BTQFBAUIBTgFCAU8BTgFCAUMBTwFDAVABTwFDAUQBUAFEAVEBUAFEAUUBUQFFAVIBUQFGAUcBUwFHAVQBUwFHAUgBVAFIAVUBVAFIAUkBVQFJAVYBVQFJAUoBVgFKAVcBVgFKAUsBVwFLAVgBVwFLAUwBWAFMAVkBWAFMAU0BWQFNAVoBWQFNAU4BWgFOAVsBWgFOAU8BWwFPAVwBWwFPAVABXAFQAV0BXAFQAVEBXQFRAV4BXQFRAVIBXgFSAV8BXgFTAVQBYAFUAWEBYAFUAVUBYQFVAWIBYQFVAVYBYgFWAWMBYgFWAVcBYwFXAWQBYwFXAVgBZAFYAWUBZAFYAVkBZQFZAWYBZQFZAVoBZgFaAWcBZgFaAVsBZwFbAWgBZwFbAVwBaAFcAWkBaAFcAV0BaQFdAWoBaQFdAV4BagFeAWsBagFeAV8BawFfAWwBawFgAWEBbQFhAW4BbQFhAWIBbgFiAW8BbgFiAWMBbwFjAXABbwFjAWQBcAFkAXEBcAFkAWUBcQFlAXIBcQFlAWYBcgFmAXMBcgFmAWcBcwFnAXQBcwFnAWgBdAFoAXUBdAFoAWkBdQFpAXYBdQFpAWoBdgFqAXcBdgFqAWsBdwFrAXgBdwFrAWwBeAFsAXkBeAFtAW4BegFuAXsBegFuAW8BewFvAXwBewFvAXABfAFwAX0BfAFwAXEBfQFxAX4BfQFxAXIBfgFyAX8BfgFyAXMBfwFzAYABfwFzAXQBgAF0AYEBgAF0AXUBgQF1AYIBgQF1AXYBggF2AYMBggF2AXcBgwF3AYQBgwF3AXgBhAF4AYUBhAF4AXkBhQF5AYYBhQGHAcQAxQCHAcUAxgCHAcYAxwCHAccAyACHAcgAyQCHAckAygCHAcoAywCHAcsAzACHAcwAzQCHAc0AzgCHAc4AzwCHAc8A0AA=",
60
+ "tint": "body",
61
+ "depth": "center"
62
+ }
63
+ ]
64
+ };
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Mods de un ot: paquetes que le cambian el aspecto (un orco, un robot…) sin tocar su ficha base.
3
+ * Viven en `look.character.mods`; quitar un mod devuelve el ot a como era.
4
+ *
5
+ * {
6
+ * id: 'orc', v: 1, name: { es: 'Orco', en: 'Orc' }, by: '7ots',
7
+ * character: { body: { color: '#7aa64a' }, brows: { type: 'angled' }, accessories: [{ id: 'horns' }] },
8
+ * parts: [{
9
+ * id: 'tusks', anchor: 'mouth', layer: 'front',
10
+ * svg: '<path d="…" fill="{color}"/>', // 2D: coordenadas de un ot de 120 de ancho, origen en el ancla
11
+ * view: [-30, -20, 60, 30], // caja del dibujo (para el respaldo 3D en plano)
12
+ * glb: 'data:model/gltf-binary;base64,…' | 'https://…/tusks.glb', // 3D (opcional)
13
+ * tint: 'color' | 'body' | 'none', // color del glb: el de la pieza, el de la piel o el suyo
14
+ * depth: 'front' | 'center', // 3D: pegado a la cara o en el centro del cuerpo
15
+ * color, color2, x, y, scale, rot, flip,
16
+ * }],
17
+ * }
18
+ *
19
+ * Un mod puede traer además un cuerpo completo (3D) que reemplaza al ot procedural:
20
+ * model: { url: 'https://…/muneco.vrm' | '/mods/<hash>.vrm' | data:glb, scale, y, rot }
21
+ * (VRM o GLB; ver src/character3d/model.js). En 2D se sigue viendo el ot con su parche.
22
+ *
23
+ * `character` es un parche de la ficha (normalizeCharacter lo sanea); sus `accessories` se suman a
24
+ * los del ot. En el SVG valen {color} {color2} {body} {bodyDark} {bodyLight} {line}. El glb usa las
25
+ * mismas unidades que el SVG (1 = 1 px de un ot de 120 de ancho) con y hacia arriba.
26
+ *
27
+ * Todo pasa por aquí antes de guardarse o pintarse: el SVG se reescribe con una lista cerrada de
28
+ * etiquetas y atributos (sin scripts, eventos, enlaces ni estilos) y el glb solo puede ser https o
29
+ * un data: de glb.
30
+ */
31
+
32
+ export const MOD_ANCHORS = ['top', 'eyes', 'nose', 'mouth', 'neck', 'center', 'bottom', 'shoulder'];
33
+ export const MAX_MODS = 4;
34
+ const MAX_PARTS = 8;
35
+ const MAX_SVG = 20000;
36
+ const MAX_GLB = 1_500_000;
37
+
38
+ const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
39
+ const num = (v, def, min, max) => {
40
+ const x = Number(v);
41
+ return Number.isFinite(x) ? Math.min(max, Math.max(min, x)) : def;
42
+ };
43
+ const hex = (v, def) => (/^#[0-9a-f]{6}$/i.test(String(v ?? '')) ? String(v).toLowerCase() : def);
44
+ const str = (v, n) => String(v ?? '').replace(/[\u0000-\u001f<>]/g, '').trim().slice(0, n);
45
+
46
+ // ── SVG ──
47
+ const TAGS = new Set(['g', 'path', 'circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon', 'defs', 'lineargradient', 'radialgradient', 'stop']);
48
+ const CASE = { lineargradient: 'linearGradient', radialgradient: 'radialGradient' };
49
+ const ATTRS = new Set([
50
+ 'd', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'width', 'height', 'points', 'transform',
51
+ 'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-width', 'stroke-opacity', 'stroke-linecap', 'stroke-linejoin', 'stroke-dasharray', 'opacity',
52
+ 'id', 'offset', 'stop-color', 'stop-opacity', 'gradientUnits', 'gradientTransform', 'fx', 'fy',
53
+ ]);
54
+ const ATTR_CASE = Object.fromEntries([...ATTRS].map((a) => [a.toLowerCase(), a]));
55
+ const SAFE_VALUE = /^[\w\s#.,%+\-{}()]*$/;
56
+
57
+ /**
58
+ * SVG de una pieza, reescrito desde cero: solo formas, grupos y degradados, con atributos de la
59
+ * lista; los ids (y sus url(#…)) llevan el prefijo del mod para no chocar con los de la página.
60
+ */
61
+ export function sanitizeSvg(svg, prefix = 'm') {
62
+ const src = String(svg ?? '').slice(0, MAX_SVG);
63
+ const pre = String(prefix).replace(/[^\w-]/g, '');
64
+ const out = [];
65
+ const stack = [];
66
+ const re = /<(\/?)([a-zA-Z][\w-]*)((?:\s+[a-zA-Z][\w:-]*\s*=\s*(?:"[^"<>]*"|'[^'<>]*'))*)\s*(\/?)>/g;
67
+ let m;
68
+ while ((m = re.exec(src))) {
69
+ const [, close, rawTag, attrs, self] = m;
70
+ const tag = rawTag.toLowerCase();
71
+ if (!TAGS.has(tag)) continue;
72
+ const name = CASE[tag] || tag;
73
+ if (close) {
74
+ const i = stack.lastIndexOf(name);
75
+ if (i < 0) continue;
76
+ while (stack.length > i) out.push(`</${stack.pop()}>`);
77
+ continue;
78
+ }
79
+ let a = '';
80
+ const ar = /([a-zA-Z][\w:-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
81
+ let x;
82
+ while ((x = ar.exec(attrs))) {
83
+ const an = ATTR_CASE[x[1].toLowerCase()];
84
+ let v = (x[2] ?? x[3] ?? '').trim();
85
+ if (!an || v.length > 4000) continue;
86
+ const urls = v.match(/url\(([^)]*)\)/gi) || [];
87
+ if (urls.some((u) => !/^url\(#[\w-]+\)$/i.test(u))) continue;
88
+ if (!SAFE_VALUE.test(v) || /(javascript|expression|data)\s*:/i.test(v)) continue;
89
+ v = v.replace(/url\(#([\w-]+)\)/gi, `url(#${pre}-$1)`);
90
+ if (an === 'id') v = `${pre}-${v.replace(/[^\w-]/g, '')}`;
91
+ a += ` ${an}="${v}"`;
92
+ }
93
+ if (self) out.push(`<${name}${a}/>`);
94
+ else {
95
+ out.push(`<${name}${a}>`);
96
+ stack.push(name);
97
+ }
98
+ }
99
+ while (stack.length) out.push(`</${stack.pop()}>`);
100
+ return out.join('');
101
+ }
102
+
103
+ // ── mods ──
104
+ const glbUrl = (v) => {
105
+ const s = String(v ?? '').trim();
106
+ if (!s) return '';
107
+ if (/^data:model\/gltf-binary;base64,[A-Za-z0-9+/=]+$/.test(s)) return s.length <= MAX_GLB ? s : '';
108
+ try {
109
+ const u = new URL(s);
110
+ return u.protocol === 'https:' && s.length <= 2000 ? u.href : '';
111
+ } catch {
112
+ return '';
113
+ }
114
+ };
115
+
116
+ /** Cuerpo completo: https, data: de glb, o un archivo subido al pet (/mods/<hash>.vrm|glb). */
117
+ const modelOf = (v) => {
118
+ if (!isObj(v)) return null;
119
+ const s = String(v.url ?? '').trim();
120
+ const url = /^\/mods\/[a-f0-9]{8,64}\.(vrm|glb)$/.test(s) ? s : glbUrl(s);
121
+ if (!url) return null;
122
+ return { url, scale: num(v.scale, 1, 0.2, 3), y: num(v.y, 0, -1, 1), rot: num(v.rot, 0, -180, 180) };
123
+ };
124
+
125
+ const nameOf = (v) => {
126
+ if (isObj(v)) {
127
+ const o = {};
128
+ for (const k of ['en', 'es', 'pt']) if (v[k]) o[k] = str(v[k], 40);
129
+ return Object.keys(o).length ? o : '';
130
+ }
131
+ return str(v, 40);
132
+ };
133
+
134
+ const PATCH_KEYS = ['body', 'eyes', 'brows', 'mouth', 'cheeks', 'finish', 'texture', 'outline', 'accessories'];
135
+
136
+ function normalizePart(p, modId) {
137
+ if (!isObj(p) || !/^[a-z0-9-]{1,24}$/.test(String(p.id))) return null;
138
+ const view = Array.isArray(p.view) && p.view.length === 4 ? p.view.map((v, i) => num(v, 0, i < 2 ? -400 : 1, 400)) : [-60, -60, 120, 120];
139
+ const part = {
140
+ id: p.id,
141
+ anchor: MOD_ANCHORS.includes(p.anchor) ? p.anchor : 'center',
142
+ layer: p.layer === 'back' ? 'back' : 'front',
143
+ svg: sanitizeSvg(p.svg, `m-${modId}-${p.id}`),
144
+ view,
145
+ glb: glbUrl(p.glb),
146
+ tint: ['color', 'body', 'none'].includes(p.tint) ? p.tint : 'none',
147
+ depth: p.depth === 'center' ? 'center' : 'front',
148
+ color: hex(p.color, '#1f2937'),
149
+ color2: hex(p.color2, '#ffffff'),
150
+ x: num(p.x, 0, -120, 120),
151
+ y: num(p.y, 0, -120, 120),
152
+ scale: num(p.scale, 1, 0.2, 3),
153
+ rot: num(p.rot, 0, -180, 180),
154
+ flip: !!p.flip,
155
+ };
156
+ return part.svg || part.glb ? part : null;
157
+ }
158
+
159
+ /** Un mod saneado, o null si no sirve. */
160
+ export function normalizeMod(m) {
161
+ if (!isObj(m) || !/^[a-z0-9-]{1,32}$/.test(String(m.id))) return null;
162
+ const patch = {};
163
+ if (isObj(m.character)) for (const k of PATCH_KEYS) if (m.character[k] !== undefined) patch[k] = m.character[k];
164
+ // el parche se sanea al aplicarse (normalizeCharacter); aquí solo se acota su tamaño
165
+ const character = JSON.stringify(patch).length <= 4000 ? JSON.parse(JSON.stringify(patch)) : {};
166
+ const parts = (Array.isArray(m.parts) ? m.parts : []).slice(0, MAX_PARTS).map((p) => normalizePart(p, m.id)).filter(Boolean);
167
+ const seen = new Set();
168
+ const model = modelOf(m.model);
169
+ return {
170
+ id: m.id,
171
+ v: 1,
172
+ name: nameOf(m.name) || m.id,
173
+ ...(m.by ? { by: str(m.by, 40) } : {}),
174
+ character,
175
+ parts: parts.filter((p) => !seen.has(p.id) && seen.add(p.id)),
176
+ ...(model ? { model } : {}),
177
+ };
178
+ }
179
+
180
+ /** Lista de mods saneada (sin repetidos, como mucho MAX_MODS). */
181
+ export function normalizeMods(list) {
182
+ if (!Array.isArray(list)) return [];
183
+ const seen = new Set();
184
+ const out = [];
185
+ for (const m of list) {
186
+ const n = normalizeMod(m);
187
+ if (!n || seen.has(n.id)) continue;
188
+ seen.add(n.id);
189
+ out.push(n);
190
+ if (out.length >= MAX_MODS) break;
191
+ }
192
+ return out;
193
+ }
194
+
195
+ /** Nombre del mod en un idioma. */
196
+ export const modName = (m, lang = 'en') => (isObj(m.name) ? m.name[lang] || m.name.en || m.name.es || Object.values(m.name)[0] : m.name) || m.id;
197
+
198
+ const merge = (a, b) => {
199
+ if (!isObj(a) || !isObj(b)) return b === undefined ? a : b;
200
+ const o = { ...a };
201
+ for (const [k, v] of Object.entries(b)) o[k] = merge(a[k], v);
202
+ return o;
203
+ };
204
+
205
+ /**
206
+ * La ficha que se pinta: la base con los parches de sus mods (en orden) y sus piezas como
207
+ * accesorios `mod:<mod>/<pieza>`. `normalize` es normalizeCharacter (sin dependencia circular).
208
+ * La ficha devuelta lleva `_modParts` con los datos de esas piezas (los lee modPart).
209
+ */
210
+ export function applyMods(spec, normalize) {
211
+ const mods = spec?.mods;
212
+ if (!Array.isArray(mods) || !mods.length) return spec;
213
+ let s = spec;
214
+ const extra = [];
215
+ const parts = {};
216
+ let model = null;
217
+ for (const m of mods) {
218
+ if (m.model) model = m.model; // el último cuerpo completo manda
219
+ const { accessories, ...patch } = m.character || {};
220
+ s = normalize({ ...merge(s, patch), accessories: [...s.accessories, ...(Array.isArray(accessories) ? accessories.filter((a) => !s.accessories.some((b) => b.id === a?.id)) : [])] });
221
+ for (const p of m.parts) {
222
+ const id = `mod:${m.id}/${p.id}`;
223
+ parts[id] = p;
224
+ extra.push({ id, x: p.x, y: p.y, scale: p.scale, rot: p.rot, flip: p.flip, color: p.color, color2: p.color2 });
225
+ }
226
+ }
227
+ // colores que fija un mod: el estilo artístico no los remapea (si no, un orco Bauhaus queda azul y amarillo)
228
+ const keep = {};
229
+ for (const m of mods) for (const [k, v] of Object.entries(m.character || {})) if (v && typeof v === 'object') for (const f of Object.keys(v)) if (/color|iris/i.test(f)) keep[`${k}.${f}`] = true;
230
+ return { ...s, mods, accessories: [...s.accessories, ...extra], _modParts: parts, _modColors: keep, ...(model ? { _model: model } : {}) };
231
+ }
232
+
233
+ /**
234
+ * Definición de accesorio (como las de parts.js) para una pieza de mod, o undefined.
235
+ * shade: la función de parts.js (para los tonos de la piel).
236
+ */
237
+ export function modPart(spec, id, shade) {
238
+ const p = spec._modParts?.[id];
239
+ if (!p) return undefined;
240
+ return {
241
+ id,
242
+ anchor: p.anchor,
243
+ layer: p.layer,
244
+ mod: p,
245
+ draw: (c) => {
246
+ const body = c.body || '#888888';
247
+ const vars = { color: c.color, color2: c.color2, body, bodyDark: shade(body, -0.22), bodyLight: shade(body, 0.25), line: shade(body, -0.5) };
248
+ return `<g transform="scale(${Math.round(c.k * 1000) / 1000})">${p.svg.replace(/\{(color2?|body|bodyDark|bodyLight|line)\}/g, (_, k) => vars[k])}</g>`;
249
+ },
250
+ };
251
+ }