@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.
- package/README.md +1 -1
- package/cli/7ots.mjs +28 -4
- 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
package/src/character/styles.js
CHANGED
|
@@ -45,6 +45,23 @@ export const STYLES = [
|
|
|
45
45
|
{ id: 'blueprint', label: { es: 'Plano técnico', en: 'Blueprint', pt: 'Planta técnica' } },
|
|
46
46
|
{ id: 'pointillism', label: { es: 'Puntillismo', en: 'Pointillism', pt: 'Pontilhismo' } },
|
|
47
47
|
{ id: 'woodcut', label: { es: 'Xilografía', en: 'Woodcut', pt: 'Xilogravura' } },
|
|
48
|
+
{ id: 'riso', label: { es: 'Risografía', en: 'Riso print', pt: 'Risografia' } },
|
|
49
|
+
{ id: 'dotmatrix', label: { es: 'Matriz verde', en: 'Dot matrix', pt: 'Matriz verde' } },
|
|
50
|
+
{ id: 'chalk', label: { es: 'Tiza', en: 'Chalk', pt: 'Giz' } },
|
|
51
|
+
{ id: 'sticker', label: { es: 'Calcomanía', en: 'Sticker', pt: 'Adesivo' } },
|
|
52
|
+
{ id: 'felt', label: { es: 'Fieltro', en: 'Felt', pt: 'Feltro' } },
|
|
53
|
+
{ id: 'papercut', label: { es: 'Papel recortado', en: 'Papercut', pt: 'Papel recortado' } },
|
|
54
|
+
{ id: 'lowpoly', label: { es: 'Low poly', en: 'Low poly', pt: 'Low poly' } },
|
|
55
|
+
{ id: 'marble', label: { es: 'Mármol', en: 'Marble', pt: 'Mármore' } },
|
|
56
|
+
{ id: 'candy', label: { es: 'Gomita', en: 'Gummy', pt: 'Bala de goma' } },
|
|
57
|
+
{ id: 'holo', label: { es: 'Holográfico', en: 'Holographic', pt: 'Holográfico' } },
|
|
58
|
+
{ id: 'sumie', label: { es: 'Sumi-e', en: 'Sumi-e', pt: 'Sumi-e' } },
|
|
59
|
+
{ id: 'tarot', label: { es: 'Tarot', en: 'Tarot', pt: 'Tarô' } },
|
|
60
|
+
{ id: 'retro70', label: { es: 'Años 70', en: 'Seventies', pt: 'Anos 70' } },
|
|
61
|
+
{ id: 'mosaic', label: { es: 'Mosaico', en: 'Mosaic', pt: 'Mosaico' } },
|
|
62
|
+
{ id: 'neonsign', label: { es: 'Letrero de neón', en: 'Neon sign', pt: 'Letreiro neon' } },
|
|
63
|
+
{ id: 'graffiti', label: { es: 'Grafiti', en: 'Graffiti', pt: 'Grafite' } },
|
|
64
|
+
{ id: 'thermal', label: { es: 'Cámara térmica', en: 'Thermal', pt: 'Câmera térmica' } },
|
|
48
65
|
];
|
|
49
66
|
export const STYLE_IDS = STYLES.map((s) => s.id);
|
|
50
67
|
|
|
@@ -113,6 +130,12 @@ const PAL = {
|
|
|
113
130
|
nouveau: ['#8a9a6b', '#c98a7a', '#c9a24a', '#4f7a74', '#efe2c4', '#b5654a', '#6e4a5e', '#a8b48c', '#d9b98a'],
|
|
114
131
|
cubism: ['#c8a046', '#7a5232', '#7c7a46', '#4e6a86', '#b5583a', '#e6d6b0', '#2e2a26', '#8aa0a8'],
|
|
115
132
|
crayon: ['#e8352b', '#f58a1f', '#f6d32b', '#4caf3c', '#2f6fd6', '#8a4fc4', '#f27bb5', '#8b5a2b', '#5fc8e8', '#222222'],
|
|
133
|
+
riso: ['#ff48b0', '#0078bf', '#ffe800', '#00a95c', '#ff6c2f', '#765ba7'],
|
|
134
|
+
dotmatrix: ['#081820', '#346856', '#88c070', '#e0f8d0'],
|
|
135
|
+
tarot: ['#1c2a5a', '#c9a23a', '#efe3c4', '#a3262e', '#2f7f7a', '#6b3a7a', '#e07a3a'],
|
|
136
|
+
retro70: ['#e8762c', '#f2b33d', '#8a4b2a', '#c94f2c', '#d9c58a', '#6b7a3a', '#4a2c1e', '#3f7f7a'],
|
|
137
|
+
mosaic: ['#b5452e', '#d98a3a', '#e6c26a', '#3f7a8a', '#2c4a6e', '#6a8a4a', '#efe4cc', '#7a3a4a', '#3a3530'],
|
|
138
|
+
graffiti: ['#ff2e63', '#08d9d6', '#f9ed32', '#7cfc00', '#ff8c00', '#9b5de5', '#00bbf9', '#ffffff', '#111111'],
|
|
116
139
|
};
|
|
117
140
|
|
|
118
141
|
/** Mezcla lineal de dos colores (t=0 → a). */
|
|
@@ -127,6 +150,22 @@ const clampHsl = (c, s0, s1, l0, l1) => {
|
|
|
127
150
|
return fromHsl(h, Math.min(s1, Math.max(s0, s)), Math.min(l1, Math.max(l0, l)));
|
|
128
151
|
};
|
|
129
152
|
|
|
153
|
+
/** Tubo de neón del color: mismo tono a tope (los grises van a rosa). */
|
|
154
|
+
const neonTube = (c) => {
|
|
155
|
+
const [h, s] = toHsl(c);
|
|
156
|
+
return fromHsl(s < 0.15 ? 322 : h, 1, 0.62);
|
|
157
|
+
};
|
|
158
|
+
/** Trazado (M…Z) de una estrella de cinco puntas de radio `r`. */
|
|
159
|
+
function star5(x, y, r) {
|
|
160
|
+
let d = '';
|
|
161
|
+
for (let i = 0; i < 10; i++) {
|
|
162
|
+
const a = -Math.PI / 2 + (i * Math.PI) / 5;
|
|
163
|
+
const k = i % 2 ? r * 0.42 : r;
|
|
164
|
+
d += `${i ? 'L' : 'M'}${n(x + Math.cos(a) * k)} ${n(y + Math.sin(a) * k)}`;
|
|
165
|
+
}
|
|
166
|
+
return d + 'Z';
|
|
167
|
+
}
|
|
168
|
+
|
|
130
169
|
/** Primario Bauhaus de un color: por tono (los grises van a negro o crema). */
|
|
131
170
|
function bauhausOf(hex) {
|
|
132
171
|
const [h, s, l] = toHsl(hex);
|
|
@@ -262,6 +301,139 @@ const LOOKS = {
|
|
|
262
301
|
return luminance(c) > 0.5 ? '#ecdfc4' : '#15120f';
|
|
263
302
|
},
|
|
264
303
|
},
|
|
304
|
+
riso: {
|
|
305
|
+
finish: 'flat', texture: 'none', ink: '#0078bf', lw: 2.6,
|
|
306
|
+
eyes: { white: '#f7f1e3', ink: '#1d2a6b' },
|
|
307
|
+
map: (c, role, spec) => {
|
|
308
|
+
if (role === 'cheek' || role === 'lip') return '#ff48b0';
|
|
309
|
+
if (role === 'brow') return '#0078bf';
|
|
310
|
+
if (role === 'pattern') return nearest(c, PAL.riso, [nearest(spec.body.color, PAL.riso, ['#0078bf'])]);
|
|
311
|
+
return nearest(c, PAL.riso, role === 'body' ? ['#0078bf'] : []);
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
dotmatrix: {
|
|
315
|
+
finish: 'flat', texture: 'none', ink: '#081820', lw: 4,
|
|
316
|
+
eyes: { white: '#e0f8d0', ink: '#081820', iris: '#081820' },
|
|
317
|
+
map: (c, role) => {
|
|
318
|
+
if (role === 'body') return '#88c070';
|
|
319
|
+
if (role === 'pattern' || role === 'cheek' || role === 'lip') return '#346856';
|
|
320
|
+
if (role === 'brow' || role === 'iris') return '#081820';
|
|
321
|
+
return PAL.dotmatrix[Math.min(3, Math.floor(luminance(c) * 4))];
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
chalk: {
|
|
325
|
+
finish: 'flat', texture: 'none', ink: '#f4f4ee', lw: 3,
|
|
326
|
+
eyes: { white: '#f4f4ee', ink: '#22352b' },
|
|
327
|
+
map: (c, role) => {
|
|
328
|
+
if (role === 'brow' || role === 'lip') return '#f4f4ee';
|
|
329
|
+
if (role === 'iris') return clampHsl(c, 0.3, 0.55, 0.35, 0.5);
|
|
330
|
+
return clampHsl(c, 0.35, 0.65, 0.68, 0.8);
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
sticker: {
|
|
334
|
+
finish: 'flat', texture: 'none', ink: '#26222b', lw: 2.4,
|
|
335
|
+
eyes: { white: '#ffffff', ink: '#1a1720' },
|
|
336
|
+
map: (c, role) => (role === 'brow' ? '#1a1720' : role === 'cheek' ? '#ff6f9c' : clampHsl(c, 0.7, 0.95, 0.46, 0.62)),
|
|
337
|
+
},
|
|
338
|
+
felt: {
|
|
339
|
+
finish: 'flat', texture: 'none', ink: '', lw: 0,
|
|
340
|
+
eyes: { white: '#f6f1e6', ink: '#2a2420' },
|
|
341
|
+
map: (c, role) => (role === 'brow' ? '#3a302a' : role === 'iris' ? clampHsl(c, 0.25, 0.5, 0.25, 0.42) : clampHsl(c, 0.28, 0.55, 0.42, 0.64)),
|
|
342
|
+
},
|
|
343
|
+
papercut: {
|
|
344
|
+
finish: 'flat', texture: 'none', ink: '', lw: 0,
|
|
345
|
+
eyes: { white: '#fffdf8', ink: '#3a3340' },
|
|
346
|
+
map: (c, role) => (role === 'brow' ? '#3a3340' : role === 'iris' ? clampHsl(c, 0.3, 0.55, 0.3, 0.45) : clampHsl(c, 0.35, 0.62, 0.6, 0.78)),
|
|
347
|
+
},
|
|
348
|
+
lowpoly: {
|
|
349
|
+
finish: 'flat', texture: 'none', ink: '', lw: 0,
|
|
350
|
+
eyes: { white: '#f5f7fa', ink: '#1d2330' },
|
|
351
|
+
map: (c, role) => (role === 'brow' ? '#1d2330' : clampHsl(c, 0.35, 0.75, 0.36, 0.6)),
|
|
352
|
+
},
|
|
353
|
+
marble: {
|
|
354
|
+
finish: 'flat', texture: 'none', ink: '#8d877c', lw: 1.8,
|
|
355
|
+
eyes: { white: '#f7f4ee', ink: '#4f4a43' },
|
|
356
|
+
map: (c, role) => {
|
|
357
|
+
if (role === 'body') return mix(grey(c, 0.86, 0.95), c, 0.08);
|
|
358
|
+
if (role === 'pattern') return mix(grey(c, 0.7, 0.85), c, 0.1);
|
|
359
|
+
if (role === 'iris') return grey(c, 0.35, 0.6);
|
|
360
|
+
if (role === 'cheek') return '#e2c4b8';
|
|
361
|
+
if (role === 'lip') return '#b4a69a';
|
|
362
|
+
if (role === 'brow') return '#7a746a';
|
|
363
|
+
return mix(c, '#c9a45a', 0.7); // lo que lleva puesto, dorado a la hoja
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
candy: {
|
|
367
|
+
finish: 'flat', texture: 'none', ink: (s) => mix(s.body.color, '#3a0a26', 0.45), lw: 2.4,
|
|
368
|
+
eyes: { white: '#ffffff', ink: '#3a1530' },
|
|
369
|
+
map: (c, role) => (role === 'brow' ? '#3a1530' : role === 'cheek' ? '#ff7fb0' : clampHsl(c, 0.72, 1, 0.56, 0.7)),
|
|
370
|
+
},
|
|
371
|
+
holo: {
|
|
372
|
+
finish: 'flat', texture: 'none', ink: '#6f74a8', lw: 2,
|
|
373
|
+
eyes: { white: '#fbfbff', ink: '#272a48' },
|
|
374
|
+
map: (c, role) => (role === 'brow' ? '#272a48' : role === 'iris' ? clampHsl(c, 0.5, 0.8, 0.4, 0.55) : clampHsl(c, 0.25, 0.5, 0.74, 0.86)),
|
|
375
|
+
},
|
|
376
|
+
sumie: {
|
|
377
|
+
finish: 'flat', texture: 'none', ink: '#1d1b19', lw: 3.6,
|
|
378
|
+
eyes: { white: '#f3eee2', ink: '#1d1b19', iris: '#1d1b19' },
|
|
379
|
+
map: (c, role) => {
|
|
380
|
+
if (role === 'body') return grey(c, 0.62, 0.9);
|
|
381
|
+
if (role === 'pattern') return grey(c, 0.3, 0.58);
|
|
382
|
+
if (role === 'cheek') return '#c96b5c';
|
|
383
|
+
if (role === 'lip' || role === 'brow') return '#1d1b19';
|
|
384
|
+
return grey(c, 0.15, 0.85);
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
tarot: {
|
|
388
|
+
finish: 'flat', texture: 'none', ink: '#1a1430', lw: 3,
|
|
389
|
+
eyes: { white: '#efe3c4', ink: '#1a1430' },
|
|
390
|
+
map: (c, role) => {
|
|
391
|
+
if (role === 'cheek') return '#d9877a';
|
|
392
|
+
if (role === 'lip') return '#a3262e';
|
|
393
|
+
if (role === 'brow') return '#1a1430';
|
|
394
|
+
return nearest(c, PAL.tarot, role === 'body' ? ['#1c2a5a', '#efe3c4'] : []);
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
retro70: {
|
|
398
|
+
finish: 'flat', texture: 'none', ink: '#4a2c1e', lw: 3,
|
|
399
|
+
eyes: { white: '#fbf1dc', ink: '#3a2216' },
|
|
400
|
+
map: (c, role) => (role === 'cheek' ? '#e8762c' : role === 'brow' ? '#3a2216' : nearest(c, PAL.retro70, role === 'body' ? ['#4a2c1e', '#d9c58a'] : [])),
|
|
401
|
+
},
|
|
402
|
+
mosaic: {
|
|
403
|
+
finish: 'flat', texture: 'none', ink: '#3a3530', lw: 4,
|
|
404
|
+
eyes: { white: '#efe4cc', ink: '#2a2622' },
|
|
405
|
+
map: (c, role) => (role === 'brow' ? '#2a2622' : nearest(c, PAL.mosaic, role === 'body' ? ['#efe4cc', '#3a3530'] : [])),
|
|
406
|
+
},
|
|
407
|
+
neonsign: {
|
|
408
|
+
// cuerpo casi negro; el contorno es el tubo, del color del cuerpo original
|
|
409
|
+
finish: 'flat', texture: 'none', ink: (s) => neonTube(s.body.color), lw: 3.4,
|
|
410
|
+
eyes: { white: '#f8f0ff', ink: '#120f18' },
|
|
411
|
+
map: (c, role, spec) => {
|
|
412
|
+
if (role === 'body') return mix(c, '#0b0b0b', 0.82);
|
|
413
|
+
if (role === 'pattern') return mix(c, '#0b0b0b', 0.62);
|
|
414
|
+
if (role === 'brow' || role === 'lip') return neonTube(spec.body.color);
|
|
415
|
+
if (role === 'cheek') return fromHsl((toHsl(neonTube(spec.body.color))[0] + 40) % 360, 1, 0.66);
|
|
416
|
+
return neonTube(c);
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
graffiti: {
|
|
420
|
+
finish: 'flat', texture: 'none', ink: '#111111', lw: 5.5,
|
|
421
|
+
eyes: { white: '#ffffff', ink: '#111111' },
|
|
422
|
+
map: (c, role) => (role === 'brow' ? '#111111' : role === 'cheek' ? '#ff2e63' : nearest(c, PAL.graffiti, role === 'body' ? ['#ffffff', '#111111'] : [])),
|
|
423
|
+
},
|
|
424
|
+
thermal: {
|
|
425
|
+
// el filtro pasa la luminancia a la paleta «ironbow»: claro = caliente, oscuro = frío
|
|
426
|
+
finish: 'flat', texture: 'none', ink: '', lw: 0,
|
|
427
|
+
eyes: { white: '#fffbe0', ink: '#0a0520', iris: '#8a1a8a' },
|
|
428
|
+
map: (c, role) => {
|
|
429
|
+
if (role === 'body') return '#ff7a1a';
|
|
430
|
+
if (role === 'pattern' || role === 'lip') return '#ffb000';
|
|
431
|
+
if (role === 'cheek') return '#ffe46a';
|
|
432
|
+
if (role === 'brow') return '#2a0a6a';
|
|
433
|
+
if (role === 'iris') return '#8a1a8a';
|
|
434
|
+
return mix('#120838', '#4a2aa8', luminance(c)); // lo que lleva puesto está frío
|
|
435
|
+
},
|
|
436
|
+
},
|
|
265
437
|
};
|
|
266
438
|
|
|
267
439
|
/** Semilla del estilo para un ot: la misma que su piel (no cambia al arrastrar puntos). */
|
|
@@ -279,22 +451,24 @@ export function stylizeSpec(spec) {
|
|
|
279
451
|
const look = LOOKS[id];
|
|
280
452
|
if (!look) return spec;
|
|
281
453
|
const s = structuredClone(spec);
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
s.body.
|
|
285
|
-
s.
|
|
454
|
+
const keep = spec._modColors || {};
|
|
455
|
+
const m = (c, role, path) => (c && !keep[path] ? look.map(c, role, spec) : c);
|
|
456
|
+
s.body.color = m(spec.body.color, 'body', 'body.color');
|
|
457
|
+
s.body.patternColor = m(spec.body.patternColor, 'pattern', 'body.patternColor');
|
|
458
|
+
s.eyes.iris = m(spec.eyes.iris, 'iris', 'eyes.iris');
|
|
286
459
|
Object.assign(s.eyes, look.eyes);
|
|
287
|
-
s.cheeks.color = m(spec.cheeks.color, 'cheek');
|
|
288
|
-
s.mouth.lipColor = m(spec.mouth.lipColor, 'lip');
|
|
289
|
-
if (spec.mouth.color) s.mouth.color = m(spec.mouth.color, 'brow');
|
|
290
|
-
if (spec.brows.color) s.brows.color = m(spec.brows.color, 'brow');
|
|
460
|
+
s.cheeks.color = m(spec.cheeks.color, 'cheek', 'cheeks.color');
|
|
461
|
+
s.mouth.lipColor = m(spec.mouth.lipColor, 'lip', 'mouth.lipColor');
|
|
462
|
+
if (spec.mouth.color) s.mouth.color = m(spec.mouth.color, 'brow', 'mouth.color');
|
|
463
|
+
if (spec.brows.color) s.brows.color = m(spec.brows.color, 'brow', 'brows.color');
|
|
291
464
|
for (const a of s.accessories) {
|
|
465
|
+
if (a.id.startsWith('mod:')) continue; // las piezas de un mod traen sus colores
|
|
292
466
|
a.color = m(a.color, 'acc');
|
|
293
467
|
a.color2 = m(a.color2, 'acc');
|
|
294
468
|
}
|
|
295
469
|
s.finish = look.finish;
|
|
296
470
|
s.texture = look.texture;
|
|
297
|
-
s._style = { id, seed: styleSeed(spec), ink: look.ink, lw: look.lw };
|
|
471
|
+
s._style = { id, seed: styleSeed(spec), ink: typeof look.ink === 'function' ? look.ink(s) : look.ink, lw: look.lw };
|
|
298
472
|
return s;
|
|
299
473
|
}
|
|
300
474
|
|
|
@@ -464,6 +638,133 @@ function filterBody(id, seed, q, animate) {
|
|
|
464
638
|
+ '<feMerge><feMergeNode in="un"/><feMergeNode in="dj"/></feMerge>';
|
|
465
639
|
case 'woodcut':
|
|
466
640
|
return full ? wobble(seed, 'SourceGraphic', 2, 0.05) + grain(seed, '#efe4cc', 1.4, 0.7) + '<feMerge><feMergeNode in="wb"/><feMergeNode in="gr"/></feMerge>' : '';
|
|
641
|
+
case 'riso': {
|
|
642
|
+
// dos tambores: la tinta rosa sale corrida bajo el azul; papel sin entintar en motas
|
|
643
|
+
const mis = '<feFlood flood-color="#ff48b0" flood-opacity=".8"/><feComposite in2="SourceAlpha" operator="in"/><feOffset dx="3.5" dy="-2.5" result="mis"/>';
|
|
644
|
+
return full
|
|
645
|
+
? mis + grain(seed, '#f7f1e3', 1.5, 0.85) + '<feMerge><feMergeNode in="mis"/><feMergeNode in="SourceGraphic"/><feMergeNode in="gr"/></feMerge>'
|
|
646
|
+
: mis + '<feMerge><feMergeNode in="mis"/><feMergeNode in="SourceGraphic"/></feMerge>';
|
|
647
|
+
}
|
|
648
|
+
case 'dotmatrix':
|
|
649
|
+
// pantalla de 4 verdes: bloques de 6 y cada bloque al verde más cercano por luminancia
|
|
650
|
+
return '<feFlood x="-160" y="-200" width="2" height="2" flood-color="#000"/><feComposite width="6" height="6"/><feTile result="tile"/>'
|
|
651
|
+
+ '<feComposite in="SourceGraphic" in2="tile" operator="in"/><feMorphology operator="dilate" radius="2"/>'
|
|
652
|
+
+ '<feComponentTransfer><feFuncA type="discrete" tableValues="0 1 1"/></feComponentTransfer>'
|
|
653
|
+
+ '<feColorMatrix values=".2126 .7152 .0722 0 0 .2126 .7152 .0722 0 0 .2126 .7152 .0722 0 0 0 0 0 1 0"/>'
|
|
654
|
+
+ '<feComponentTransfer><feFuncR type="discrete" tableValues=".03 .2 .53 .88"/><feFuncG type="discrete" tableValues=".09 .41 .75 .97"/><feFuncB type="discrete" tableValues=".13 .34 .44 .82"/></feComponentTransfer>';
|
|
655
|
+
case 'chalk':
|
|
656
|
+
if (!full) return '';
|
|
657
|
+
// tiza: trazo tembloroso, el pizarrón asomando entre los granos y polvo blanco alrededor
|
|
658
|
+
return wobble(seed, 'SourceGraphic', 2.4, 0.05)
|
|
659
|
+
+ `<feTurbulence type="fractalNoise" baseFrequency=".75" numOctaves="2" seed="${(seed + 7) % 997}" result="cn"/>`
|
|
660
|
+
+ '<feColorMatrix in="cn" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -5 0 0 0 3.6" result="ca"/><feComposite in="wb" in2="ca" operator="in" result="ch"/>'
|
|
661
|
+
+ '<feGaussianBlur in="SourceAlpha" stdDeviation="3.5" result="db"/><feFlood flood-color="#f4f4ee" flood-opacity=".2"/><feComposite in2="db" operator="in" result="dust"/>'
|
|
662
|
+
+ '<feMerge><feMergeNode in="dust"/><feMergeNode in="ch"/></feMerge>';
|
|
663
|
+
case 'sticker': {
|
|
664
|
+
// troquel: borde blanco redondeado (alfa difuminado y cortado) y, en full, la sombra de la pegatina
|
|
665
|
+
const cut = '<feGaussianBlur in="SourceAlpha" stdDeviation="3.5"/><feComponentTransfer result="cut"><feFuncA type="linear" slope="14" intercept="-.7"/></feComponentTransfer>'
|
|
666
|
+
+ '<feFlood flood-color="#ffffff"/><feComposite in2="cut" operator="in" result="wh"/>';
|
|
667
|
+
return full
|
|
668
|
+
? cut + '<feGaussianBlur in="cut" stdDeviation="2.4"/><feOffset dx="1.5" dy="3.5"/><feColorMatrix values="0 0 0 0 .1 0 0 0 0 .08 0 0 0 0 .14 0 0 0 .38 0" result="sh"/>'
|
|
669
|
+
+ '<feMerge><feMergeNode in="sh"/><feMergeNode in="wh"/><feMergeNode in="SourceGraphic"/></feMerge>'
|
|
670
|
+
: cut + '<feMerge><feMergeNode in="wh"/><feMergeNode in="SourceGraphic"/></feMerge>';
|
|
671
|
+
}
|
|
672
|
+
case 'felt':
|
|
673
|
+
if (!full) return '';
|
|
674
|
+
// fieltro: borde de fibras sueltas y pelusa clara y oscura
|
|
675
|
+
return wobble(seed, 'SourceGraphic', 3, 0.45)
|
|
676
|
+
+ grain(seed, '#ffffff', 1.5, 1.3, 'gl') + grain(seed + 3, '#000000', 1.3, 0.9, 'gd')
|
|
677
|
+
+ '<feMerge><feMergeNode in="wb"/><feMergeNode in="gl"/><feMergeNode in="gd"/></feMerge>';
|
|
678
|
+
case 'papercut': {
|
|
679
|
+
// cartulina recortada que flota sobre el fondo: sombra suave (barata, también en lite)
|
|
680
|
+
const sh = '<feGaussianBlur in="SourceAlpha" stdDeviation="2.6"/><feOffset dx="2" dy="4"/><feColorMatrix values="0 0 0 0 .12 0 0 0 0 .08 0 0 0 0 .18 0 0 0 .4 0" result="sh"/>';
|
|
681
|
+
return full
|
|
682
|
+
? sh + grain(seed, '#ffffff', 1.1, 1.4) + '<feMerge><feMergeNode in="sh"/><feMergeNode in="SourceGraphic"/><feMergeNode in="gr"/></feMerge>'
|
|
683
|
+
: sh + '<feMerge><feMergeNode in="sh"/><feMergeNode in="SourceGraphic"/></feMerge>';
|
|
684
|
+
}
|
|
685
|
+
case 'marble': {
|
|
686
|
+
if (!full) return '';
|
|
687
|
+
// vetas finas (pliegues de turbulencia estirada) y luz difusa de escultura
|
|
688
|
+
const light = '<feDistantLight azimuth="225" elevation="58"/>';
|
|
689
|
+
return `<feTurbulence type="turbulence" baseFrequency=".012 .03" numOctaves="4" seed="${seed % 997}" result="vn"/>`
|
|
690
|
+
+ '<feColorMatrix in="vn" values="0 0 0 0 .38 0 0 0 0 .36 0 0 0 0 .34 -5 0 0 0 .62" result="va"/><feComposite in="va" in2="SourceAlpha" operator="in" result="vein"/>'
|
|
691
|
+
+ '<feGaussianBlur in="SourceAlpha" stdDeviation="6" result="b"/>'
|
|
692
|
+
+ `<feDiffuseLighting in="b" surfaceScale="5" diffuseConstant="1.1" lighting-color="#fff" result="dl">${light}</feDiffuseLighting>`
|
|
693
|
+
+ '<feComposite in="SourceGraphic" in2="dl" operator="arithmetic" k1=".7" k2=".34" result="sh"/>'
|
|
694
|
+
+ '<feMerge><feMergeNode in="sh"/><feMergeNode in="vein"/></feMerge>';
|
|
695
|
+
}
|
|
696
|
+
case 'candy': {
|
|
697
|
+
if (!full) return '';
|
|
698
|
+
// gomita: brillo especular de gelatina sobre la silueta redondeada y algo de transparencia
|
|
699
|
+
const light = '<feDistantLight azimuth="235" elevation="50"/>';
|
|
700
|
+
return '<feGaussianBlur in="SourceAlpha" stdDeviation="7" result="b"/>'
|
|
701
|
+
+ `<feSpecularLighting in="b" surfaceScale="7" specularConstant=".9" specularExponent="26" lighting-color="#fff" result="sp">${light}</feSpecularLighting>`
|
|
702
|
+
+ '<feComposite in="sp" in2="SourceAlpha" operator="in" result="sp2"/>'
|
|
703
|
+
+ '<feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 .94 0" result="tr"/>'
|
|
704
|
+
+ '<feComposite in="tr" in2="sp2" operator="arithmetic" k2="1" k3=".85"/>';
|
|
705
|
+
}
|
|
706
|
+
case 'holo': {
|
|
707
|
+
if (!full) return '';
|
|
708
|
+
// tornasol: el tono gira solo (al animar) y destellos de purpurina
|
|
709
|
+
const anim = animate ? '<animate attributeName="values" values="0;360" dur="7s" repeatCount="indefinite"/>' : '';
|
|
710
|
+
return `<feColorMatrix in="SourceGraphic" type="hueRotate" values="0" result="hr">${anim}</feColorMatrix>`
|
|
711
|
+
+ `<feTurbulence type="fractalNoise" baseFrequency="1.6" numOctaves="1" seed="${seed % 997}" result="gn"/>`
|
|
712
|
+
+ '<feColorMatrix in="gn" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 9 0 0 0 -5.5" result="ga"/><feComposite in="ga" in2="SourceAlpha" operator="in" result="gl"/>'
|
|
713
|
+
+ '<feMerge><feMergeNode in="hr"/><feMergeNode in="gl"/></feMerge>';
|
|
714
|
+
}
|
|
715
|
+
case 'sumie':
|
|
716
|
+
// tinta china: casi sin color; en full el trazo tiembla y se corre en el papel de arroz
|
|
717
|
+
return '<feColorMatrix in="SourceGraphic" type="saturate" values=".15" result="bw"/>'
|
|
718
|
+
+ (full
|
|
719
|
+
? wobble(seed, 'bw', 2.6, 0.04) + '<feGaussianBlur in="wb" stdDeviation="1.8"/><feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 .5 0" result="bl"/>'
|
|
720
|
+
+ grain(seed, '#f3eee2', 1.3, 0.8) + '<feMerge><feMergeNode in="bl"/><feMergeNode in="wb"/><feMergeNode in="gr"/></feMerge>'
|
|
721
|
+
: '');
|
|
722
|
+
case 'tarot':
|
|
723
|
+
return full ? wobble(seed, 'SourceGraphic', 1.2, 0.05) + grain(seed, '#5a4020', 1.2) + '<feMerge><feMergeNode in="wb"/><feMergeNode in="gr"/></feMerge>' : '';
|
|
724
|
+
case 'retro70':
|
|
725
|
+
// foto desteñida: negros levantados, tono cálido y grano
|
|
726
|
+
return full
|
|
727
|
+
? '<feColorMatrix in="SourceGraphic" values=".9 .06 0 0 .07 0 .86 .02 0 .05 0 .04 .76 0 .04 0 0 0 1 0" result="fd"/>' + grain(seed, '#5a3a1e', 1.2) + '<feMerge><feMergeNode in="fd"/><feMergeNode in="gr"/></feMerge>'
|
|
728
|
+
: '';
|
|
729
|
+
case 'mosaic': {
|
|
730
|
+
// teselas de 6 (5 + junta de mortero); en full cada tesela cambia de tono y queda puesta a mano
|
|
731
|
+
let f = '<feFlood flood-color="#9a9282"/><feComposite in2="SourceAlpha" operator="in" result="gt"/>';
|
|
732
|
+
let src = 'SourceGraphic';
|
|
733
|
+
if (full) {
|
|
734
|
+
f += `<feTurbulence type="fractalNoise" baseFrequency=".21" numOctaves="1" seed="${seed % 997}"/><feColorMatrix values="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1" result="tg"/>`
|
|
735
|
+
+ '<feComposite in="SourceGraphic" in2="tg" operator="arithmetic" k1=".6" k2=".72" result="tv"/>';
|
|
736
|
+
src = 'tv';
|
|
737
|
+
}
|
|
738
|
+
f += '<feFlood x="-160" y="-200" width="2" height="2" flood-color="#000"/><feComposite width="6" height="6"/><feTile result="tile"/>'
|
|
739
|
+
+ `<feComposite in="${src}" in2="tile" operator="in"/><feMorphology operator="dilate" radius="1.6"/>`
|
|
740
|
+
+ '<feComponentTransfer result="ts"><feFuncA type="discrete" tableValues="0 1 1"/></feComponentTransfer>';
|
|
741
|
+
return f + (full ? wobble(seed, 'ts', 1.8, 0.11) + '<feMerge><feMergeNode in="gt"/><feMergeNode in="wb"/></feMerge>' : '<feMerge><feMergeNode in="gt"/><feMergeNode in="ts"/></feMerge>');
|
|
742
|
+
}
|
|
743
|
+
case 'neonsign': {
|
|
744
|
+
if (!full) return '';
|
|
745
|
+
// resplandor solo de lo luminoso (el tubo); al animar, el halo parpadea como un letrero viejo
|
|
746
|
+
const flick = animate ? '<animate attributeName="slope" values="1.5;1.5;1.5;.3;1.5;1.5;1.5;1.5;.6;1.5;.2;1.5" dur="5s" repeatCount="indefinite" calcMode="discrete"/>' : '';
|
|
747
|
+
return '<feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 .7 .7 .7 0 -.55" result="br"/>'
|
|
748
|
+
+ '<feGaussianBlur in="br" stdDeviation="3" result="g1"/><feGaussianBlur in="br" stdDeviation="9" result="g2"/>'
|
|
749
|
+
+ `<feComponentTransfer in="g2" result="g3"><feFuncA type="linear" slope="1.5">${flick}</feFuncA></feComponentTransfer>`
|
|
750
|
+
+ '<feMerge><feMergeNode in="g3"/><feMergeNode in="g1"/><feMergeNode in="SourceGraphic"/></feMerge>';
|
|
751
|
+
}
|
|
752
|
+
case 'graffiti':
|
|
753
|
+
if (!full) return '';
|
|
754
|
+
// espray: niebla de gotitas alrededor del trazo
|
|
755
|
+
return '<feGaussianBlur in="SourceGraphic" stdDeviation="3.5"/><feComponentTransfer result="bl"><feFuncA type="linear" slope="2.2"/></feComponentTransfer>'
|
|
756
|
+
+ `<feTurbulence type="fractalNoise" baseFrequency="1.1" numOctaves="1" seed="${seed % 997}" result="sn"/>`
|
|
757
|
+
+ '<feColorMatrix in="sn" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12 0 0 0 -6.9" result="sm"/>'
|
|
758
|
+
+ '<feComposite in="bl" in2="sm" operator="in" result="spk"/>'
|
|
759
|
+
+ '<feMerge><feMergeNode in="spk"/><feMergeNode in="SourceGraphic"/></feMerge>';
|
|
760
|
+
case 'thermal': {
|
|
761
|
+
// cámara térmica: luminancia → paleta «ironbow» (barato: vale en lite); en full, desenfoque y ruido del sensor
|
|
762
|
+
const ramp = '<feColorMatrix values=".2126 .7152 .0722 0 0 .2126 .7152 .0722 0 0 .2126 .7152 .0722 0 0 0 0 0 1 0"/>'
|
|
763
|
+
+ '<feComponentTransfer result="rm"><feFuncR type="table" tableValues=".04 .16 .54 .91 1 1"/><feFuncG type="table" tableValues=".02 .04 .1 .25 .69 .98"/><feFuncB type="table" tableValues=".13 .42 .54 .11 0 .88"/></feComponentTransfer>';
|
|
764
|
+
return full
|
|
765
|
+
? '<feGaussianBlur in="SourceGraphic" stdDeviation="1.2"/>' + ramp + grain(seed, '#ffffff', 0.8, 1.4) + '<feMerge><feMergeNode in="rm"/><feMergeNode in="gr"/></feMerge>'
|
|
766
|
+
: ramp;
|
|
767
|
+
}
|
|
467
768
|
default:
|
|
468
769
|
return '';
|
|
469
770
|
}
|
|
@@ -660,6 +961,242 @@ function innerLayer(s, L, uid, d) {
|
|
|
660
961
|
svg += `<path d="${d}" fill="url(#${uid}-wh)" transform="translate(${n(b.w * 0.22)} ${n(b.h * 0.2)})"/>`;
|
|
661
962
|
break;
|
|
662
963
|
}
|
|
964
|
+
case 'riso': {
|
|
965
|
+
// trama de medios tonos en la sombra (más gruesa cuanto más adentro) y papel sin tinta como brillo
|
|
966
|
+
defs += `<pattern id="${uid}-h1" width="5" height="5" patternUnits="userSpaceOnUse" patternTransform="rotate(22)"><circle cx="2.5" cy="2.5" r="1" fill="#0078bf" opacity=".6"/></pattern>`;
|
|
967
|
+
defs += `<pattern id="${uid}-h2" width="5" height="5" patternUnits="userSpaceOnUse" patternTransform="rotate(22)"><circle cx="2.5" cy="2.5" r="1.9" fill="#0078bf" opacity=".6"/></pattern>`;
|
|
968
|
+
svg += `<path d="${d}" fill="url(#${uid}-h1)" transform="translate(${n(b.w * 0.12)} ${n(b.h * 0.1)})"/>`;
|
|
969
|
+
svg += `<path d="${d}" fill="url(#${uid}-h2)" transform="translate(${n(b.w * 0.3)} ${n(b.h * 0.26)})"/>`;
|
|
970
|
+
svg += `<ellipse cx="${n(b.x + b.w * 0.3)}" cy="${n(b.y + b.h * 0.2)}" rx="${n(b.w * 0.13)}" ry="${n(b.h * 0.07)}" fill="#f7f1e3" opacity=".75" transform="rotate(-25 ${n(b.x + b.w * 0.3)} ${n(b.y + b.h * 0.2)})"/>`;
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
case 'dotmatrix': {
|
|
974
|
+
// sombra tramada en damero y sombra llena, alineadas a los bloques de 6 del filtro
|
|
975
|
+
const q6 = (v) => Math.round(v / 6) * 6;
|
|
976
|
+
defs += `<pattern id="${uid}-dt" width="12" height="12" patternUnits="userSpaceOnUse"><rect width="6" height="6" fill="#346856"/><rect x="6" y="6" width="6" height="6" fill="#346856"/></pattern>`;
|
|
977
|
+
svg += `<path d="${d}" fill="url(#${uid}-dt)" transform="translate(${q6(b.w * 0.16)} ${q6(b.h * 0.14)})"/>`;
|
|
978
|
+
svg += `<path d="${d}" fill="#346856" transform="translate(${q6(b.w * 0.34)} ${q6(b.h * 0.3)})"/>`;
|
|
979
|
+
svg += `<rect x="${q6(b.x + b.w * 0.2)}" y="${q6(b.y + b.h * 0.12)}" width="12" height="6" fill="#e0f8d0"/><rect x="${q6(b.x + b.w * 0.2)}" y="${q6(b.y + b.h * 0.12) + 6}" width="6" height="6" fill="#e0f8d0"/>`;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
case 'chalk': {
|
|
983
|
+
// rayado de tiza: trazos claros en la luz y el pizarrón asomando en rayas en la sombra
|
|
984
|
+
let lt = '';
|
|
985
|
+
for (let i = 0; i < 12; i++) {
|
|
986
|
+
const x = b.x + b.w * (0.08 + rnd() * 0.5);
|
|
987
|
+
const y = b.y + b.h * (0.08 + rnd() * 0.45);
|
|
988
|
+
const l = b.w * (0.08 + rnd() * 0.12);
|
|
989
|
+
lt += `M${n(x)} ${n(y)}l${n(l)} ${n(-l * 0.5)}`;
|
|
990
|
+
}
|
|
991
|
+
svg += `<path d="${lt}" stroke="#fff" stroke-width="2.4" stroke-linecap="round" opacity=".35"/>`;
|
|
992
|
+
defs += `<pattern id="${uid}-ch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(-30)"><rect width="6" height="2.2" fill="#22352b"/></pattern>`;
|
|
993
|
+
svg += `<path d="${d}" fill="url(#${uid}-ch)" opacity=".55" transform="translate(${n(b.w * 0.2)} ${n(b.h * 0.18)})"/>`;
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
case 'sticker': {
|
|
997
|
+
// vinilo brillante: dos franjas de reflejo en diagonal y un destello
|
|
998
|
+
const x0 = b.x - 10;
|
|
999
|
+
const y0 = b.y - 10;
|
|
1000
|
+
svg += `<path d="M${n(x0)} ${n(b.y + b.h * 0.42)}L${n(b.x + b.w * 0.5)} ${n(y0)}L${n(b.x + b.w * 0.72)} ${n(y0)}L${n(x0)} ${n(b.y + b.h * 0.6)}Z" fill="#fff" opacity=".3"/>`;
|
|
1001
|
+
svg += `<path d="M${n(x0)} ${n(b.y + b.h * 0.68)}L${n(b.x + b.w * 0.84)} ${n(y0)}L${n(b.x + b.w * 0.9)} ${n(y0)}L${n(x0)} ${n(b.y + b.h * 0.74)}Z" fill="#fff" opacity=".22"/>`;
|
|
1002
|
+
svg += `<path d="${d}" fill="#000" opacity=".08" transform="translate(${n(b.w * 0.1)} ${n(b.h * 0.12)})"/>`;
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
case 'felt': {
|
|
1006
|
+
// puntada corrida por dentro del borde en hilo de otro tono, relleno abullonado
|
|
1007
|
+
const k = Math.max(0.7, 1 - 18 / b.w);
|
|
1008
|
+
const cx = L.cx;
|
|
1009
|
+
const cy = b.y + b.h * 0.55;
|
|
1010
|
+
const thread = luminance(s.body.color) > 0.45 ? mix(s.body.color, '#2a2420', 0.55) : mix(s.body.color, '#fff8ec', 0.7);
|
|
1011
|
+
defs += `<radialGradient id="${uid}-fp" cx=".35" cy=".3" r=".75"><stop offset="0" stop-color="#fff" stop-opacity=".22"/><stop offset=".6" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".18"/></radialGradient>`;
|
|
1012
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-fp)"/>`;
|
|
1013
|
+
svg += `<path d="${d}" fill="none" stroke="${thread}" stroke-width="${n(2.4 / k)}" stroke-dasharray="${n(5.5 / k)} ${n(4 / k)}" stroke-linecap="round" transform="translate(${n(cx * (1 - k))} ${n(cy * (1 - k))}) scale(${n(k)})"/>`;
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
case 'papercut': {
|
|
1017
|
+
// capas de cartulina: el contorno repetido más chico, cada capa más clara y con su sombra
|
|
1018
|
+
defs += `<filter id="${uid}-ps" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="1.2" dy="2.4" stdDeviation="1.6" flood-color="#2a1f3a" flood-opacity=".35"/></filter>`;
|
|
1019
|
+
const cx = L.cx;
|
|
1020
|
+
const cy = b.y + b.h * 0.78;
|
|
1021
|
+
[0.8, 0.6, 0.4].forEach((k, i) => {
|
|
1022
|
+
svg += `<path d="${d}" fill="${mix(s.body.color, '#ffffff', 0.16 * (i + 1))}" filter="url(#${uid}-ps)" transform="translate(${n(cx * (1 - k))} ${n(cy * (1 - k))}) scale(${k})"/>`;
|
|
1023
|
+
});
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
case 'lowpoly': {
|
|
1027
|
+
// facetas: rejilla con jitter partida en triángulos; cada uno con la luz de su normal sobre una cúpula
|
|
1028
|
+
const x0 = b.x - 8;
|
|
1029
|
+
const y0 = b.y - 8;
|
|
1030
|
+
const w = b.w + 16;
|
|
1031
|
+
const h = b.h + 16;
|
|
1032
|
+
const cols = 5;
|
|
1033
|
+
const rows = 6;
|
|
1034
|
+
const cy = b.y + b.h * 0.5;
|
|
1035
|
+
const P = [];
|
|
1036
|
+
for (let r = 0; r <= rows; r++) {
|
|
1037
|
+
const row = [];
|
|
1038
|
+
for (let c = 0; c <= cols; c++) {
|
|
1039
|
+
const edge = r === 0 || c === 0 || r === rows || c === cols;
|
|
1040
|
+
const x = x0 + (w * (c + (edge ? 0 : (rnd() - 0.5) * 0.7))) / cols;
|
|
1041
|
+
const y = y0 + (h * (r + (edge ? 0 : (rnd() - 0.5) * 0.7))) / rows;
|
|
1042
|
+
const u = (x - L.cx) / (w / 2);
|
|
1043
|
+
const v = (y - cy) / (h / 2);
|
|
1044
|
+
row.push([x, y, Math.sqrt(Math.max(0, 1 - u * u - v * v)) * Math.min(w, h) * 0.6 + rnd() * 5]);
|
|
1045
|
+
}
|
|
1046
|
+
P.push(row);
|
|
1047
|
+
}
|
|
1048
|
+
const Lv = [-0.48, -0.62, 0.62];
|
|
1049
|
+
const tri = (a, e, c) => {
|
|
1050
|
+
const ux = e[0] - a[0], uy = e[1] - a[1], uz = e[2] - a[2];
|
|
1051
|
+
const vx = c[0] - a[0], vy = c[1] - a[1], vz = c[2] - a[2];
|
|
1052
|
+
let nx = uy * vz - uz * vy;
|
|
1053
|
+
let ny = uz * vx - ux * vz;
|
|
1054
|
+
let nz = ux * vy - uy * vx;
|
|
1055
|
+
if (nz < 0) [nx, ny, nz] = [-nx, -ny, -nz];
|
|
1056
|
+
const sh = (nx * Lv[0] + ny * Lv[1] + nz * Lv[2]) / (Math.hypot(nx, ny, nz) || 1);
|
|
1057
|
+
const lit = sh > 0.62;
|
|
1058
|
+
const col = lit ? '#fff' : '#000';
|
|
1059
|
+
const op = Math.min(0.5, lit ? (sh - 0.62) * 1.2 : (0.62 - sh) * 0.6);
|
|
1060
|
+
return `<path d="M${n(a[0])} ${n(a[1])}L${n(e[0])} ${n(e[1])}L${n(c[0])} ${n(c[1])}Z" fill="${col}" stroke="${col}" stroke-width=".5" stroke-linejoin="round" opacity="${n(op)}"/>`;
|
|
1061
|
+
};
|
|
1062
|
+
for (let r = 0; r < rows; r++) {
|
|
1063
|
+
for (let c = 0; c < cols; c++) {
|
|
1064
|
+
const a = P[r][c], e = P[r][c + 1], f = P[r + 1][c + 1], g = P[r + 1][c];
|
|
1065
|
+
svg += (r + c) % 2 ? tri(a, e, f) + tri(a, f, g) : tri(a, e, g) + tri(e, f, g);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
case 'marble': {
|
|
1071
|
+
// modelado: luz cenital arriba a la izquierda, sombra tallada y alguna veta (también en lite)
|
|
1072
|
+
defs += `<radialGradient id="${uid}-ml" cx=".34" cy=".26" r=".8"><stop offset="0" stop-color="#fff" stop-opacity=".55"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#4a443c" stop-opacity=".22"/></radialGradient>`;
|
|
1073
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-ml)"/>`;
|
|
1074
|
+
let v = '';
|
|
1075
|
+
for (let i = 0; i < 3; i++) {
|
|
1076
|
+
const y = b.y + b.h * (0.2 + i * 0.28 + rnd() * 0.1);
|
|
1077
|
+
v += `M${n(b.x - 6)} ${n(y)}C${n(b.x + b.w * 0.3)} ${n(y - 14 + rnd() * 10)} ${n(b.x + b.w * 0.55)} ${n(y + 18 * rnd())} ${n(b.x + b.w + 6)} ${n(y - 20 + rnd() * 12)}`;
|
|
1078
|
+
}
|
|
1079
|
+
svg += `<path d="${v}" fill="none" stroke="#7d776d" stroke-width=".9" opacity=".35"/>`;
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
case 'candy': {
|
|
1083
|
+
// gomita azucarada: borde interior más intenso, cristalitos de azúcar y un brillo
|
|
1084
|
+
svg += `<path d="${d}" fill="none" stroke="${mix(s.body.color, '#000', 0.25)}" stroke-width="12" opacity=".3"/>`;
|
|
1085
|
+
let sug = '';
|
|
1086
|
+
for (let i = 0; i < 46; i++) {
|
|
1087
|
+
const x = b.x + rnd() * b.w;
|
|
1088
|
+
const y = b.y + rnd() * b.h;
|
|
1089
|
+
const z = 1.3 + rnd() * 1.6;
|
|
1090
|
+
sug += `<rect x="${n(x)}" y="${n(y)}" width="${n(z)}" height="${n(z)}" transform="rotate(${Math.floor(rnd() * 90)} ${n(x)} ${n(y)})" opacity="${n(0.45 + rnd() * 0.45)}"/>`;
|
|
1091
|
+
}
|
|
1092
|
+
svg += `<g fill="#fff">${sug}</g>`;
|
|
1093
|
+
svg += `<ellipse cx="${n(b.x + b.w * 0.3)}" cy="${n(b.y + b.h * 0.2)}" rx="${n(b.w * 0.12)}" ry="${n(b.h * 0.06)}" fill="#fff" opacity=".6" transform="rotate(-30 ${n(b.x + b.w * 0.3)} ${n(b.y + b.h * 0.2)})"/>`;
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
case 'holo': {
|
|
1097
|
+
// arcoíris tornasolado en diagonal, rayas de brillo y destellos
|
|
1098
|
+
defs += `<linearGradient id="${uid}-hg" gradientUnits="userSpaceOnUse" x1="${n(b.x)}" y1="${n(b.y)}" x2="${n(b.x + b.w * 0.55)}" y2="${n(b.y + b.h * 0.55)}" spreadMethod="reflect">`
|
|
1099
|
+
+ '<stop offset="0" stop-color="#ff8fd0"/><stop offset=".22" stop-color="#ffe08a"/><stop offset=".42" stop-color="#8affc0"/><stop offset=".62" stop-color="#8ad0ff"/><stop offset=".82" stop-color="#b98aff"/><stop offset="1" stop-color="#ff8fd0"/></linearGradient>';
|
|
1100
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-hg)" opacity=".62"/>`;
|
|
1101
|
+
let sh = '';
|
|
1102
|
+
for (let i = 0; i < 5; i++) {
|
|
1103
|
+
const o = b.w * (0.1 + i * 0.22 + rnd() * 0.05);
|
|
1104
|
+
sh += `M${n(b.x + o)} ${n(b.y - 6)}l${n(-b.h * 0.6)} ${n(b.h * 1.2)}`;
|
|
1105
|
+
}
|
|
1106
|
+
svg += `<path d="${sh}" stroke="#fff" stroke-width="1.4" opacity=".35"/>`;
|
|
1107
|
+
let sp = '';
|
|
1108
|
+
for (let i = 0; i < 3; i++) {
|
|
1109
|
+
const x = b.x + b.w * (0.15 + rnd() * 0.7);
|
|
1110
|
+
const y = b.y + b.h * (0.15 + rnd() * 0.7);
|
|
1111
|
+
const r = 3 + rnd() * 3;
|
|
1112
|
+
sp += `M${n(x)} ${n(y - r)}Q${n(x)} ${n(y)} ${n(x + r)} ${n(y)}Q${n(x)} ${n(y)} ${n(x)} ${n(y + r)}Q${n(x)} ${n(y)} ${n(x - r)} ${n(y)}Q${n(x)} ${n(y)} ${n(x)} ${n(y - r)}Z`;
|
|
1113
|
+
}
|
|
1114
|
+
svg += `<path d="${sp}" fill="#fff" opacity=".9"/>`;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
case 'sumie': {
|
|
1118
|
+
// aguada: la tinta se junta abajo y una pincelada seca cruza el cuerpo
|
|
1119
|
+
defs += `<linearGradient id="${uid}-sw" x1="0" y1="0" x2="0" y2="1"><stop offset=".35" stop-color="#1d1b19" stop-opacity="0"/><stop offset="1" stop-color="#1d1b19" stop-opacity=".4"/></linearGradient>`;
|
|
1120
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-sw)"/>`;
|
|
1121
|
+
const x0 = b.x + b.w * 0.12;
|
|
1122
|
+
const y0 = b.y + b.h * 0.95;
|
|
1123
|
+
let br = '';
|
|
1124
|
+
for (let i = 0; i < 5; i++) {
|
|
1125
|
+
const o = (i - 2) * b.w * 0.028;
|
|
1126
|
+
br += `<path d="M${n(x0 + o)} ${n(y0 + o)}C${n(b.x + b.w * 0.05 + o)} ${n(b.y + b.h * 0.5)} ${n(b.x + b.w * 0.3 + o)} ${n(b.y + b.h * 0.15)} ${n(b.x + b.w * 0.62 + o)} ${n(b.y + b.h * 0.08 + o)}" stroke-width="${n(2 + rnd() * 3)}" stroke-dasharray="${Math.floor(20 + rnd() * 40)} ${Math.floor(2 + rnd() * 6)} ${Math.floor(10 + rnd() * 30)} ${Math.floor(3 + rnd() * 5)}"/>`;
|
|
1127
|
+
}
|
|
1128
|
+
svg += `<g fill="none" stroke="#1d1b19" stroke-linecap="round" opacity=".2">${br}</g>`;
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
case 'tarot': {
|
|
1132
|
+
// cielo de carta: filete de puntos dorados por dentro del borde, estrellas y una luna
|
|
1133
|
+
const gold = s.body.color === '#c9a23a' ? '#efe3c4' : '#c9a23a';
|
|
1134
|
+
const k = Math.max(0.72, 1 - 16 / b.w);
|
|
1135
|
+
const cy = b.y + b.h * 0.55;
|
|
1136
|
+
svg += `<path d="${d}" fill="none" stroke="${gold}" stroke-width="${n(2.2 / k)}" stroke-dasharray="0 ${n(6 / k)}" stroke-linecap="round" transform="translate(${n(L.cx * (1 - k))} ${n(cy * (1 - k))}) scale(${n(k)})"/>`;
|
|
1137
|
+
let st = '';
|
|
1138
|
+
for (let i = 0; i < 5; i++) {
|
|
1139
|
+
const x = b.x + b.w * (0.15 + rnd() * 0.7);
|
|
1140
|
+
const y = b.y + b.h * (0.45 + rnd() * 0.45);
|
|
1141
|
+
const r = 2.2 + rnd() * 2.6;
|
|
1142
|
+
st += star5(x, y, r);
|
|
1143
|
+
}
|
|
1144
|
+
svg += `<path d="${st}" fill="${gold}"/>`;
|
|
1145
|
+
const mx = b.x + b.w * 0.78;
|
|
1146
|
+
const my = b.y + b.h * 0.3;
|
|
1147
|
+
svg += `<path d="M${n(mx)} ${n(my - 7)}A7 7 0 1 0 ${n(mx)} ${n(my + 7)}A5.4 5.4 0 1 1 ${n(mx)} ${n(my - 7)}Z" fill="${gold}"/>`;
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
case 'retro70': {
|
|
1151
|
+
// franjas setenteras que cruzan el cuerpo en curva
|
|
1152
|
+
const cols = ['#4a2c1e', '#c94f2c', '#e8762c', '#f2b33d', '#d9c58a'].filter((c) => c !== s.body.color).slice(1, 4);
|
|
1153
|
+
const x0 = b.x - 12;
|
|
1154
|
+
const x1 = b.x + b.w + 12;
|
|
1155
|
+
cols.forEach((c, i) => {
|
|
1156
|
+
const y = b.y + b.h * (0.7 + i * 0.085);
|
|
1157
|
+
svg += `<path d="M${n(x0)} ${n(y + 14)}C${n(b.x + b.w * 0.3)} ${n(y + 16)} ${n(b.x + b.w * 0.6)} ${n(y - 22)} ${n(x1)} ${n(y - 30)}" fill="none" stroke="${c}" stroke-width="${n(b.h * 0.075)}"/>`;
|
|
1158
|
+
});
|
|
1159
|
+
svg += `<path d="${d}" fill="#fff" opacity=".12" transform="translate(${n(-b.w * 0.08)} ${n(-b.h * 0.1)})"/>`;
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
case 'mosaic': {
|
|
1163
|
+
// opus vermiculatum: una fila de teselas de otro color siguiendo el borde, sombra y luz
|
|
1164
|
+
const k = Math.max(0.7, 1 - 20 / b.w);
|
|
1165
|
+
const cy = b.y + b.h * 0.55;
|
|
1166
|
+
svg += `<path d="${d}" fill="#000" opacity=".2" transform="translate(${n(b.w * 0.2)} ${n(b.h * 0.18)})"/>`;
|
|
1167
|
+
svg += `<path d="${d}" fill="none" stroke="${s.body.patternColor && s.body.patternColor !== s.body.color ? s.body.patternColor : '#efe4cc'}" stroke-width="${n(6 / k)}" opacity=".9" transform="translate(${n(L.cx * (1 - k))} ${n(cy * (1 - k))}) scale(${n(k)})"/>`;
|
|
1168
|
+
svg += `<ellipse cx="${n(b.x + b.w * 0.3)}" cy="${n(b.y + b.h * 0.22)}" rx="${n(b.w * 0.14)}" ry="${n(b.h * 0.08)}" fill="#fff" opacity=".35"/>`;
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
case 'neonsign': {
|
|
1172
|
+
// segundo tubo por dentro (con sus cortes) y el reflejo del vidrio
|
|
1173
|
+
const tube = neonTube(s.body.color);
|
|
1174
|
+
const k = Math.max(0.6, 1 - 26 / b.w);
|
|
1175
|
+
const cy = b.y + b.h * 0.55;
|
|
1176
|
+
svg += `<path d="${d}" fill="none" stroke="${tube}" stroke-width="${n(2.2 / k)}" stroke-dasharray="${n(70 / k)} ${n(9 / k)}" stroke-linecap="round" opacity=".75" transform="translate(${n(L.cx * (1 - k))} ${n(cy * (1 - k))}) scale(${n(k)})"/>`;
|
|
1177
|
+
defs += `<linearGradient id="${uid}-ng" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="${tube}" stop-opacity=".22"/><stop offset=".5" stop-color="${tube}" stop-opacity="0"/></linearGradient>`;
|
|
1178
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-ng)"/>`;
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
case 'graffiti': {
|
|
1182
|
+
// relleno de espray: se funde a otro color abajo, burbujas de brillo y reflejo blanco
|
|
1183
|
+
const c2 = PAL.graffiti.filter((c) => c !== s.body.color && c !== '#ffffff' && c !== '#111111')[Math.floor(rnd() * 6)];
|
|
1184
|
+
defs += `<linearGradient id="${uid}-gf" x1="0" y1="0" x2="0" y2="1"><stop offset=".4" stop-color="${c2}" stop-opacity="0"/><stop offset=".85" stop-color="${c2}" stop-opacity=".95"/></linearGradient>`;
|
|
1185
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-gf)"/>`;
|
|
1186
|
+
let bub = '';
|
|
1187
|
+
for (let i = 0; i < 4; i++) bub += `<circle cx="${n(b.x + b.w * (0.15 + rnd() * 0.7))}" cy="${n(b.y + b.h * (0.35 + rnd() * 0.5))}" r="${n(2.5 + rnd() * 4)}" fill="none" stroke="#fff" stroke-width="1.6" opacity=".55"/>`;
|
|
1188
|
+
svg += bub;
|
|
1189
|
+
const gx = b.x + b.w * 0.2;
|
|
1190
|
+
const gy = b.y + b.h * 0.3;
|
|
1191
|
+
svg += `<path d="M${n(gx)} ${n(gy + 16)}Q${n(gx)} ${n(gy)} ${n(gx + 14)} ${n(gy - 6)}" fill="none" stroke="#fff" stroke-width="4.5" stroke-linecap="round"/><circle cx="${n(gx + 22)}" cy="${n(gy - 8)}" r="2.4" fill="#fff"/>`;
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
case 'thermal': {
|
|
1195
|
+
// calor: el centro del cuerpo más caliente que los bordes
|
|
1196
|
+
defs += `<radialGradient id="${uid}-th" cx=".5" cy=".48" r=".62"><stop offset="0" stop-color="#fff" stop-opacity=".6"/><stop offset=".45" stop-color="#fff" stop-opacity=".12"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></radialGradient>`;
|
|
1197
|
+
svg += `<rect x="${n(b.x - 4)}" y="${n(b.y - 4)}" width="${n(b.w + 8)}" height="${n(b.h + 8)}" fill="url(#${uid}-th)"/>`;
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
663
1200
|
}
|
|
664
1201
|
return { defs, svg };
|
|
665
1202
|
}
|
|
@@ -691,6 +1228,53 @@ export function styleLayers(s, L, ctx) {
|
|
|
691
1228
|
const W = 300;
|
|
692
1229
|
const GROUND = 240;
|
|
693
1230
|
|
|
1231
|
+
/** Filtro de grano para un fondo: motas de un color (rgb 0..1) con la densidad de `a`/`b`. */
|
|
1232
|
+
const bdGrain = (id, rnd, rgb, a = 1.4, b = -0.72, freq = 0.75) =>
|
|
1233
|
+
`<filter id="${id}" x="0" y="0" width="100%" height="100%"><feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="2" seed="${Math.floor(rnd() * 900)}"/><feColorMatrix values="0 0 0 0 ${rgb[0]} 0 0 0 0 ${rgb[1]} 0 0 0 0 ${rgb[2]} 0 0 0 ${a} ${b}"/></filter>`;
|
|
1234
|
+
|
|
1235
|
+
/** Rejilla con jitter partida en triángulos que cubre el rectángulo; `color(x, y)` en el centroide. */
|
|
1236
|
+
function facets(rnd, x0, y0, w, h, cols, rows, color) {
|
|
1237
|
+
const P = [];
|
|
1238
|
+
for (let r = 0; r <= rows; r++) {
|
|
1239
|
+
const row = [];
|
|
1240
|
+
for (let c = 0; c <= cols; c++) {
|
|
1241
|
+
const ex = c === 0 || c === cols;
|
|
1242
|
+
const ey = r === 0 || r === rows;
|
|
1243
|
+
row.push([x0 + (w * (c + (ex ? 0 : (rnd() - 0.5) * 0.8))) / cols, y0 + (h * (r + (ey ? 0 : (rnd() - 0.5) * 0.8))) / rows]);
|
|
1244
|
+
}
|
|
1245
|
+
P.push(row);
|
|
1246
|
+
}
|
|
1247
|
+
let s = '';
|
|
1248
|
+
const t = (a, b, c) => {
|
|
1249
|
+
const col = color((a[0] + b[0] + c[0]) / 3, (a[1] + b[1] + c[1]) / 3);
|
|
1250
|
+
s += `<path d="M${n(a[0])} ${n(a[1])}L${n(b[0])} ${n(b[1])}L${n(c[0])} ${n(c[1])}Z" fill="${col}" stroke="${col}" stroke-width=".7" stroke-linejoin="round"/>`;
|
|
1251
|
+
};
|
|
1252
|
+
for (let r = 0; r < rows; r++) {
|
|
1253
|
+
for (let c = 0; c < cols; c++) {
|
|
1254
|
+
const a = P[r][c], b = P[r][c + 1], e = P[r + 1][c + 1], d = P[r + 1][c];
|
|
1255
|
+
if ((r + c) % 2) {
|
|
1256
|
+
t(a, b, e);
|
|
1257
|
+
t(a, e, d);
|
|
1258
|
+
} else {
|
|
1259
|
+
t(a, b, d);
|
|
1260
|
+
t(b, e, d);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return s;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/** Celdas de una rejilla de `size` cuyo centro cae en el círculo (para dibujar en bloques). */
|
|
1268
|
+
function blockDisc(cx, cy, r, size) {
|
|
1269
|
+
let d = '';
|
|
1270
|
+
for (let y = Math.floor((cy - r) / size) * size; y < cy + r; y += size) {
|
|
1271
|
+
for (let x = Math.floor((cx - r) / size) * size; x < cx + r; x += size) {
|
|
1272
|
+
if ((x + size / 2 - cx) ** 2 + (y + size / 2 - cy) ** 2 <= r * r) d += `M${x} ${y}h${size}v${size}h${-size}Z`;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return d;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
694
1278
|
const BACKDROPS = {
|
|
695
1279
|
ukiyoe(p, rnd) {
|
|
696
1280
|
const sunX = 190 + rnd() * 50;
|
|
@@ -1395,6 +1979,698 @@ const BACKDROPS = {
|
|
|
1395
1979
|
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
1396
1980
|
return s;
|
|
1397
1981
|
},
|
|
1982
|
+
riso(p, rnd) {
|
|
1983
|
+
const pk = '#ff48b0';
|
|
1984
|
+
const bl = '#0078bf';
|
|
1985
|
+
const yl = '#ffe800';
|
|
1986
|
+
const M = 'style="mix-blend-mode:multiply"';
|
|
1987
|
+
let s = `<defs>${bdGrain(`${p}g`, rnd, [0.97, 0.95, 0.9], 1.7, -0.95)}`
|
|
1988
|
+
+ `<pattern id="${p}hb" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(18)"><circle cx="3" cy="3" r="1.7" fill="${bl}"/></pattern>`
|
|
1989
|
+
+ `<pattern id="${p}hp" width="5" height="5" patternUnits="userSpaceOnUse" patternTransform="rotate(-24)"><circle cx="2.5" cy="2.5" r="1.4" fill="${pk}"/></pattern></defs>`;
|
|
1990
|
+
s += `<rect width="${W}" height="${W}" fill="#f7f1e3"/>`;
|
|
1991
|
+
// sol amarillo con su doble rosa en trama, corrido
|
|
1992
|
+
const sx = 60 + rnd() * 180;
|
|
1993
|
+
s += `<circle cx="${n(sx)}" cy="80" r="46" fill="${yl}" ${M}/><circle cx="${n(sx + 10)}" cy="73" r="46" fill="url(#${p}hp)" ${M}/>`;
|
|
1994
|
+
// nubes de una sola tinta
|
|
1995
|
+
for (let i = 0; i < 2; i++) {
|
|
1996
|
+
const cx = 40 + rnd() * 220;
|
|
1997
|
+
const cy = 40 + rnd() * 50;
|
|
1998
|
+
s += `<rect x="${n(cx - 30)}" y="${n(cy)}" width="60" height="16" rx="8" fill="${bl}" opacity=".55" ${M}/>`;
|
|
1999
|
+
}
|
|
2000
|
+
// cerros: uno azul plano, otro rosa en trama, superpuestos (la mezcla hace el violeta)
|
|
2001
|
+
const hill = (y, a) => `M-10 ${GROUND}V${n(y)}Q${n(50 + rnd() * 40)} ${n(y - a)} ${n(120 + rnd() * 30)} ${n(y)}T${n(230 + rnd() * 20)} ${n(y + rnd() * 10)}T${W + 10} ${n(y - 10)}V${GROUND}Z`;
|
|
2002
|
+
s += `<path d="${hill(170, 50)}" fill="${bl}" opacity=".9" ${M}/>`;
|
|
2003
|
+
s += `<path d="${hill(196, 40)}" fill="url(#${p}hp)" ${M}/><path d="${hill(210, 30)}" fill="${pk}" opacity=".75" ${M}/>`;
|
|
2004
|
+
// suelo: amarillo con trama azul corrida
|
|
2005
|
+
s += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="${yl}" ${M}/><rect x="3" y="${GROUND + 4}" width="${W}" height="${W - GROUND}" fill="url(#${p}hb)" opacity=".7" ${M}/>`;
|
|
2006
|
+
// flores de dos tintas
|
|
2007
|
+
for (let i = 0; i < 6; i++) {
|
|
2008
|
+
const x = rnd() < 0.5 ? 14 + rnd() * 70 : 216 + rnd() * 70;
|
|
2009
|
+
const y = GROUND + 14 + rnd() * 40;
|
|
2010
|
+
s += `<circle cx="${n(x)}" cy="${n(y)}" r="5" fill="${pk}" ${M}/><circle cx="${n(x + 2)}" cy="${n(y - 1.5)}" r="2.2" fill="${bl}" ${M}/>`;
|
|
2011
|
+
}
|
|
2012
|
+
// marcas de registro en las esquinas
|
|
2013
|
+
let reg = '';
|
|
2014
|
+
for (const [x, y] of [[14, 14], [W - 14, 14], [14, W - 14], [W - 14, W - 14]]) reg += `<circle cx="${x}" cy="${y}" r="4.5"/><path d="M${x - 8} ${y}H${x + 8}M${x} ${y - 8}V${y + 8}"/>`;
|
|
2015
|
+
s += `<g fill="none" stroke="${bl}" stroke-width=".8">${reg}</g><g fill="none" stroke="${pk}" stroke-width=".8" transform="translate(1.5 -1)">${reg}</g>`;
|
|
2016
|
+
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
2017
|
+
return s;
|
|
2018
|
+
},
|
|
2019
|
+
|
|
2020
|
+
dotmatrix(p, rnd) {
|
|
2021
|
+
const [c0, c1, c2, c3] = PAL.dotmatrix;
|
|
2022
|
+
const B = 6;
|
|
2023
|
+
let s = `<defs><pattern id="${p}dt" width="12" height="12" patternUnits="userSpaceOnUse"><rect width="6" height="6" fill="${c2}"/><rect x="6" y="6" width="6" height="6" fill="${c2}"/></pattern>`
|
|
2024
|
+
+ `<pattern id="${p}px" width="3" height="3" patternUnits="userSpaceOnUse"><path d="M0 0H3M0 0V3" stroke="${c0}" stroke-width=".5" opacity=".12"/></pattern></defs>`;
|
|
2025
|
+
s += `<g shape-rendering="crispEdges"><rect width="${W}" height="${W}" fill="${c3}"/>`;
|
|
2026
|
+
// horizonte tramado
|
|
2027
|
+
s += `<rect y="132" width="${W}" height="36" fill="url(#${p}dt)"/><rect y="168" width="${W}" height="${GROUND - 168}" fill="${c2}"/>`;
|
|
2028
|
+
// sol y nubes en bloques
|
|
2029
|
+
const sx = 6 * Math.floor(30 + rnd() * 12);
|
|
2030
|
+
s += `<path d="${blockDisc(sx, 66, 28, B)}" fill="${c2}"/><path d="${blockDisc(sx, 66, 17, B)}" fill="${c3}"/>`;
|
|
2031
|
+
for (let i = 0; i < 3; i++) {
|
|
2032
|
+
const cx = 6 * Math.floor(4 + rnd() * 40);
|
|
2033
|
+
const cy = 6 * Math.floor(4 + rnd() * 10);
|
|
2034
|
+
s += `<path d="${blockDisc(cx, cy, 11, B)}${blockDisc(cx + 14, cy + 3, 9, B)}${blockDisc(cx - 13, cy + 4, 8, B)}" fill="${c2}"/>`;
|
|
2035
|
+
}
|
|
2036
|
+
// colinas escalonadas
|
|
2037
|
+
let hills = '';
|
|
2038
|
+
const ph = rnd() * 6;
|
|
2039
|
+
for (let x = 0; x < W; x += B) {
|
|
2040
|
+
const h = Math.round((Math.sin(x / 34 + ph) * 18 + Math.sin(x / 13) * 6 + 40) / B) * B;
|
|
2041
|
+
hills += `M${x} ${GROUND - h}h${B}V${GROUND}h${-B}Z`;
|
|
2042
|
+
}
|
|
2043
|
+
s += `<path d="${hills}" fill="${c1}"/>`;
|
|
2044
|
+
// árbol de bloques a un lado y tubería al otro
|
|
2045
|
+
const tx = rnd() < 0.5 ? 36 : 252;
|
|
2046
|
+
s += `<rect x="${tx - 3}" y="${GROUND - 42}" width="6" height="42" fill="${c0}"/><path d="${blockDisc(tx, GROUND - 54, 20, B)}" fill="${c0}"/><path d="${blockDisc(tx - 4, GROUND - 58, 11, B)}" fill="${c1}"/>`;
|
|
2047
|
+
const px = tx < 150 ? 246 : 30;
|
|
2048
|
+
s += `<rect x="${px - 15}" y="${GROUND - 36}" width="30" height="36" fill="${c1}"/><rect x="${px - 18}" y="${GROUND - 48}" width="36" height="12" fill="${c1}"/><path d="M${px - 18} ${GROUND - 48}h36v12h-36ZM${px - 15} ${GROUND - 36}V${GROUND}M${px + 15} ${GROUND - 36}V${GROUND}" fill="none" stroke="${c0}" stroke-width="3"/><rect x="${px - 12}" y="${GROUND - 45}" width="6" height="6" fill="${c3}"/>`;
|
|
2049
|
+
// monedas flotando
|
|
2050
|
+
for (let i = 0; i < 3; i++) {
|
|
2051
|
+
const cx = 6 * Math.floor(7 + i * 16 + rnd() * 4);
|
|
2052
|
+
s += `<path d="${blockDisc(cx, 114, 7, 3)}" fill="${c1}"/><rect x="${cx - 1.5}" y="108" width="3" height="12" fill="${c3}"/>`;
|
|
2053
|
+
}
|
|
2054
|
+
// suelo de ladrillos
|
|
2055
|
+
s += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="${c1}"/>`;
|
|
2056
|
+
let br = '';
|
|
2057
|
+
for (let r = 0; r * 12 < W - GROUND; r++) {
|
|
2058
|
+
const y = GROUND + r * 12;
|
|
2059
|
+
br += `M0 ${y}H${W}`;
|
|
2060
|
+
for (let x = r % 2 ? 12 : 0; x < W; x += 24) br += `M${x} ${y}v12`;
|
|
2061
|
+
}
|
|
2062
|
+
s += `<path d="${br}" stroke="${c0}" stroke-width="2" fill="none"/><rect y="${GROUND}" width="${W}" height="3" fill="${c2}"/>`;
|
|
2063
|
+
// marcador
|
|
2064
|
+
s += `<text x="12" y="22" font-family="monospace" font-weight="bold" font-size="12" fill="${c0}">SCORE ${String(1000 + Math.floor(rnd() * 9000)).padStart(6, '0')}</text>`;
|
|
2065
|
+
let hearts = '';
|
|
2066
|
+
for (let i = 0; i < 3; i++) {
|
|
2067
|
+
const x = 246 + i * 15;
|
|
2068
|
+
hearts += `M${x} 12h3v3h-3ZM${x + 6} 12h3v3h-3ZM${x - 3} 15h15v3h-15ZM${x} 18h9v3h-9ZM${x + 3} 21h3v3h-3Z`;
|
|
2069
|
+
}
|
|
2070
|
+
s += `<path d="${hearts}" fill="${c0}"/></g>`;
|
|
2071
|
+
// rejilla de la pantalla LCD
|
|
2072
|
+
s += `<rect width="${W}" height="${W}" fill="url(#${p}px)"/>`;
|
|
2073
|
+
return s;
|
|
2074
|
+
},
|
|
2075
|
+
|
|
2076
|
+
chalk(p, rnd) {
|
|
2077
|
+
const ch = '#f4f4ee';
|
|
2078
|
+
let s = `<defs><radialGradient id="${p}bd" cx=".5" cy=".45" r=".75"><stop offset="0" stop-color="#3a5a4c"/><stop offset="1" stop-color="#1f342b"/></radialGradient>`
|
|
2079
|
+
+ `<filter id="${p}c" x="-5%" y="-5%" width="110%" height="110%"><feTurbulence type="fractalNoise" baseFrequency=".9" numOctaves="2" seed="${Math.floor(rnd() * 900)}" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="2.5" xChannelSelector="R" yChannelSelector="G" result="d"/><feColorMatrix in="n" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3.6 0 0 0 2.5" result="a"/><feComposite in="d" in2="a" operator="in"/></filter>`
|
|
2080
|
+
+ `<filter id="${p}sm" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="9"/></filter>`
|
|
2081
|
+
+ `${bdGrain(`${p}g`, rnd, [0.95, 0.96, 0.93], 1.2, -0.66, 0.9)}</defs>`;
|
|
2082
|
+
s += `<rect width="${W}" height="${W}" fill="url(#${p}bd)"/>`;
|
|
2083
|
+
// borrones de mota
|
|
2084
|
+
let sm = '';
|
|
2085
|
+
for (let i = 0; i < 5; i++) sm += `<ellipse cx="${n(30 + rnd() * 240)}" cy="${n(30 + rnd() * 200)}" rx="${n(30 + rnd() * 40)}" ry="${n(10 + rnd() * 14)}" transform="rotate(${Math.floor(rnd() * 40 - 20)})"/>`;
|
|
2086
|
+
s += `<g fill="${ch}" opacity=".07" filter="url(#${p}sm)">${sm}</g>`;
|
|
2087
|
+
// dibujos de tiza
|
|
2088
|
+
let g = '';
|
|
2089
|
+
const sx = rnd() < 0.5 ? 58 : 242;
|
|
2090
|
+
let rays = '';
|
|
2091
|
+
for (let i = 0; i < 10; i++) {
|
|
2092
|
+
const a = (i / 10) * Math.PI * 2;
|
|
2093
|
+
rays += `M${n(sx + Math.cos(a) * 24)} ${n(62 + Math.sin(a) * 24)}L${n(sx + Math.cos(a) * 34)} ${n(62 + Math.sin(a) * 34)}`;
|
|
2094
|
+
}
|
|
2095
|
+
g += `<circle cx="${sx}" cy="62" r="17" fill="none" stroke="#f6e27a" stroke-width="3"/><path d="${rays}" stroke="#f6e27a" stroke-width="3" stroke-linecap="round"/>`;
|
|
2096
|
+
const ox = sx < 150 ? 236 : 62;
|
|
2097
|
+
g += `<text x="${ox}" y="56" text-anchor="middle" font-family="'Comic Sans MS','Chalkboard SE',cursive" font-size="17" fill="${ch}">a²+b²=c²</text>`;
|
|
2098
|
+
g += `<text x="${ox}" y="84" text-anchor="middle" font-family="'Comic Sans MS','Chalkboard SE',cursive" font-size="15" fill="#f6a5c0">7 + 3 = 10</text>`;
|
|
2099
|
+
let stars = '';
|
|
2100
|
+
for (let i = 0; i < 5; i++) stars += star5(20 + rnd() * 260, 110 + rnd() * 60, 4 + rnd() * 3);
|
|
2101
|
+
g += `<path d="${stars}" fill="none" stroke="${ch}" stroke-width="1.8" stroke-linejoin="round"/>`;
|
|
2102
|
+
g += `<path d="M${ox - 38} 104q16 -14 30 0t30 0" fill="none" stroke="#9fd8f0" stroke-width="2.6" stroke-linecap="round"/>`;
|
|
2103
|
+
// suelo: raya de tiza y pasto en trazos
|
|
2104
|
+
let grass = `M8 ${GROUND + 2}Q80 ${GROUND - 3} 150 ${GROUND + 1}T${W - 8} ${GROUND}`;
|
|
2105
|
+
for (let x = 14; x < W - 10; x += 9 + rnd() * 8) grass += `M${n(x)} ${GROUND + 1}l${n(-2 + rnd() * 4)} ${n(-6 - rnd() * 6)}`;
|
|
2106
|
+
g += `<path d="${grass}" fill="none" stroke="#b6e3a0" stroke-width="2.6" stroke-linecap="round"/>`;
|
|
2107
|
+
s += `<g filter="url(#${p}c)">${g}</g>`;
|
|
2108
|
+
// marco de madera y repisa con tizas y borrador
|
|
2109
|
+
s += `<path d="M0 0H${W}V${W}H0ZM9 9V${W - 9}H${W - 9}V9Z" fill-rule="evenodd" fill="#8a5a34"/><path d="M9 9H${W - 9}V${W - 9}H9Z" fill="none" stroke="#5e3b20" stroke-width="2"/>`;
|
|
2110
|
+
s += `<rect x="0" y="${W - 22}" width="${W}" height="22" fill="#9a6a40"/><rect x="0" y="${W - 22}" width="${W}" height="4" fill="#b8844f"/>`;
|
|
2111
|
+
s += `<rect x="40" y="${W - 28}" width="22" height="6" rx="3" fill="${ch}"/><rect x="70" y="${W - 28}" width="16" height="6" rx="3" fill="#f6a5c0"/>`;
|
|
2112
|
+
s += `<rect x="210" y="${W - 34}" width="46" height="12" rx="2" fill="#d8c7a8"/><rect x="210" y="${W - 26}" width="46" height="4" fill="#4a4a52"/>`;
|
|
2113
|
+
s += `<rect width="${W}" height="${W - 22}" filter="url(#${p}g)" opacity=".5"/>`;
|
|
2114
|
+
return s;
|
|
2115
|
+
},
|
|
2116
|
+
|
|
2117
|
+
sticker(p, rnd) {
|
|
2118
|
+
let s = `<defs><filter id="${p}sh" x="-30%" y="-30%" width="160%" height="160%"><feDropShadow dx="1.5" dy="3" stdDeviation="2" flood-color="#0b2a1e" flood-opacity=".45"/></filter></defs>`;
|
|
2119
|
+
// tapete de corte con su cuadrícula, guías a 45° y regla
|
|
2120
|
+
s += `<rect width="${W}" height="${W}" fill="#2f7d62"/>`;
|
|
2121
|
+
let minor = '';
|
|
2122
|
+
let major = '';
|
|
2123
|
+
for (let v = 10; v < W; v += 10) (v % 50 ? (minor += `M${v} 0V${W}M0 ${v}H${W}`) : (major += `M${v} 0V${W}M0 ${v}H${W}`));
|
|
2124
|
+
s += `<path d="${minor}" stroke="#fff" stroke-width=".5" opacity=".14"/><path d="${major}" stroke="#fff" stroke-width="1" opacity=".32"/>`;
|
|
2125
|
+
s += `<path d="M0 ${W}L${W} 0M0 150L150 0M150 ${W}L${W} 150" stroke="#f2d24a" stroke-width=".9" opacity=".45"/>`;
|
|
2126
|
+
let nums = '';
|
|
2127
|
+
for (let v = 50; v < W; v += 50) nums += `<text x="${v + 2}" y="9">${v / 10}</text><text x="2" y="${v - 2}">${v / 10}</text>`;
|
|
2128
|
+
s += `<g font-family="sans-serif" font-size="7" fill="#fff" opacity=".6">${nums}</g>`;
|
|
2129
|
+
// pegatinas sueltas: troquel blanco, color y brillo
|
|
2130
|
+
const shapes = [
|
|
2131
|
+
(c) => `<path d="${star5(0, 0, 18)}" fill="${c}"/>`,
|
|
2132
|
+
(c) => `<path d="M0 16C-24 0 -16 -18 0 -8C16 -18 24 0 0 16Z" fill="${c}"/>`,
|
|
2133
|
+
(c) => `<path d="M4 -20L-12 4H0L-4 20L12 -4H0Z" fill="${c}"/>`,
|
|
2134
|
+
(c) => `<circle r="16" fill="${c}"/><circle cx="-5.5" cy="-3" r="2.2" fill="#1a1720"/><circle cx="5.5" cy="-3" r="2.2" fill="#1a1720"/><path d="M-7 5Q0 11 7 5" fill="none" stroke="#1a1720" stroke-width="2" stroke-linecap="round"/>`,
|
|
2135
|
+
(c) => `<path d="M-22 -12H22V8H-4L-12 16V8H-22Z" fill="${c}"/><text y="3" text-anchor="middle" font-family="sans-serif" font-weight="900" font-size="12" fill="#1a1720">WOW</text>`,
|
|
2136
|
+
(c) => `<path d="M-20 8A20 20 0 0 1 20 8" fill="none" stroke="${c}" stroke-width="6"/><path d="M-14 8A14 14 0 0 1 14 8" fill="none" stroke="#ffd23f" stroke-width="6"/><path d="M-8 8A8 8 0 0 1 8 8" fill="none" stroke="#3ec1d3" stroke-width="6"/>`,
|
|
2137
|
+
];
|
|
2138
|
+
const cols = ['#ff5d8f', '#ffd23f', '#3ec1d3', '#ff8c42', '#a77dff', '#7ed957'];
|
|
2139
|
+
const spots = [[44, 46], [256, 50], [36, 150], [264, 156], [46, 262], [254, 262]];
|
|
2140
|
+
const order = shapes.map((f, i) => [rnd(), i]).sort((a, b) => a[0] - b[0]).map((x) => x[1]);
|
|
2141
|
+
spots.forEach(([x, y], i) => {
|
|
2142
|
+
const art = shapes[order[i]](cols[(i + Math.floor(rnd() * 6)) % 6]);
|
|
2143
|
+
s += `<g transform="translate(${x} ${y}) rotate(${Math.floor(rnd() * 40 - 20)})" filter="url(#${p}sh)"><g stroke="#fff" stroke-width="9" stroke-linejoin="round" stroke-linecap="round">${art}</g>${art}<path d="M-14 -10L-6 -16" stroke="#fff" stroke-width="2.5" stroke-linecap="round" opacity=".7"/></g>`;
|
|
2144
|
+
});
|
|
2145
|
+
return s;
|
|
2146
|
+
},
|
|
2147
|
+
|
|
2148
|
+
felt(p, rnd) {
|
|
2149
|
+
let s = `<defs><pattern id="${p}ln" width="4" height="4" patternUnits="userSpaceOnUse"><path d="M0 1H4M1 0V4" stroke="#8a7a60" stroke-width=".7" opacity=".22"/></pattern>`
|
|
2150
|
+
+ `<radialGradient id="${p}vg" cx=".5" cy=".5" r=".7"><stop offset=".6" stop-color="#000" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".25"/></radialGradient></defs>`;
|
|
2151
|
+
// lino de fondo y la tela tensada dentro del bastidor
|
|
2152
|
+
s += `<rect width="${W}" height="${W}" fill="#cbbfa8"/><rect width="${W}" height="${W}" fill="url(#${p}ln)"/><rect width="${W}" height="${W}" fill="url(#${p}vg)"/>`;
|
|
2153
|
+
const R = 138;
|
|
2154
|
+
s += `<circle cx="150" cy="150" r="${R}" fill="#f3ecdc"/><circle cx="150" cy="150" r="${R}" fill="url(#${p}ln)"/>`;
|
|
2155
|
+
// bordado: sol de puntadas radiales, nubes en pespunte, flores margarita y pasto
|
|
2156
|
+
let g = '';
|
|
2157
|
+
const sx = rnd() < 0.5 ? 92 : 208;
|
|
2158
|
+
let sun = '';
|
|
2159
|
+
for (let i = 0; i < 28; i++) {
|
|
2160
|
+
const a = (i / 28) * Math.PI * 2;
|
|
2161
|
+
sun += `M${n(sx + Math.cos(a) * 3)} ${n(62 + Math.sin(a) * 3)}L${n(sx + Math.cos(a) * 15)} ${n(62 + Math.sin(a) * 15)}`;
|
|
2162
|
+
}
|
|
2163
|
+
g += `<path d="${sun}" stroke="#f2b33d" stroke-width="2.2" stroke-linecap="round"/>`;
|
|
2164
|
+
const cx = sx < 150 ? 206 : 94;
|
|
2165
|
+
g += `<path d="M${cx - 24} 74a10 10 0 0 1 8 -14a13 13 0 0 1 24 -2a10 10 0 0 1 16 16Z" fill="none" stroke="#7aa6c8" stroke-width="2" stroke-dasharray="4 2.5" stroke-linecap="round"/>`;
|
|
2166
|
+
const flower = (x, y, c) => {
|
|
2167
|
+
let pe = '';
|
|
2168
|
+
for (let i = 0; i < 6; i++) pe += `<ellipse cx="${x}" cy="${n(y - 6)}" rx="2.6" ry="5.5" transform="rotate(${i * 60} ${x} ${y})"/>`;
|
|
2169
|
+
return `<path d="M${x} ${y + 6}Q${x - 3} ${y + 20} ${x + 1} ${GROUND + 6}" fill="none" stroke="#5f8a4a" stroke-width="2"/><g fill="none" stroke="${c}" stroke-width="1.8">${pe}</g><circle cx="${x}" cy="${y}" r="2.6" fill="#f2b33d"/>`;
|
|
2170
|
+
};
|
|
2171
|
+
const fc = ['#d8708a', '#9a7ac8', '#e89a5a', '#6aa0c8'];
|
|
2172
|
+
for (let i = 0; i < 4; i++) {
|
|
2173
|
+
const x = i < 2 ? 40 + i * 28 + rnd() * 10 : 206 + (i - 2) * 30 + rnd() * 10;
|
|
2174
|
+
g += flower(n(x), n(GROUND - 30 - rnd() * 34), fc[Math.floor(rnd() * 4)]);
|
|
2175
|
+
}
|
|
2176
|
+
let grass = '';
|
|
2177
|
+
for (let x = 22; x < 280; x += 5 + rnd() * 4) grass += `M${n(x)} ${GROUND + 6}l${n(-2 + rnd() * 4)} ${n(-6 - rnd() * 7)}`;
|
|
2178
|
+
g += `<path d="${grass}" stroke="#6f9a52" stroke-width="1.8" stroke-linecap="round"/>`;
|
|
2179
|
+
g += `<path d="M14 ${GROUND + 6}Q150 ${GROUND - 2} 286 ${GROUND + 6}" fill="none" stroke="#4f7a3a" stroke-width="2.4" stroke-dasharray="6 4" stroke-linecap="round"/>`;
|
|
2180
|
+
// tierra en puntadas cruzadas
|
|
2181
|
+
let xs = '';
|
|
2182
|
+
for (let y = GROUND + 18; y < W - 20; y += 10) {
|
|
2183
|
+
for (let x = 30 + (y % 20 ? 5 : 0); x < 270; x += 10) {
|
|
2184
|
+
if ((x - 150) ** 2 + (y - 150) ** 2 < (R - 10) ** 2) xs += `M${x - 3} ${y - 3}l6 6M${x + 3} ${y - 3}l-6 6`;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
g += `<path d="${xs}" stroke="#b08a62" stroke-width="1.4" stroke-linecap="round" opacity=".8"/>`;
|
|
2188
|
+
s += g;
|
|
2189
|
+
// bastidor de madera: aro doble y tornillo arriba
|
|
2190
|
+
s += `<circle cx="150" cy="150" r="${R + 2}" fill="none" stroke="#000" stroke-width="16" opacity=".12" transform="translate(2 4)"/>`;
|
|
2191
|
+
s += `<circle cx="150" cy="150" r="${R + 2}" fill="none" stroke="#c8995e" stroke-width="13"/><circle cx="150" cy="150" r="${R + 2}" fill="none" stroke="#a87a44" stroke-width="1.4"/><circle cx="150" cy="150" r="${R + 7}" fill="none" stroke="#e2bb84" stroke-width="1.2"/>`;
|
|
2192
|
+
s += `<rect x="138" y="0" width="24" height="12" rx="2" fill="#b88a50"/><rect x="132" y="2" width="36" height="5" rx="2" fill="#9a9aa2"/><circle cx="168" cy="4.5" r="4" fill="#bdbdc4"/>`;
|
|
2193
|
+
return s;
|
|
2194
|
+
},
|
|
2195
|
+
|
|
2196
|
+
papercut(p, rnd) {
|
|
2197
|
+
const sets = [
|
|
2198
|
+
['#fde8d7', '#f7b267', '#f4845f', '#f27059', '#c46a8a', '#8a5a8a', '#5a4a7a'],
|
|
2199
|
+
['#e4f3ef', '#ffd6a5', '#b8e0d2', '#95c8b8', '#6aa89a', '#4f8a7e', '#3a6a62'],
|
|
2200
|
+
];
|
|
2201
|
+
const pal = sets[Math.floor(rnd() * sets.length)];
|
|
2202
|
+
let s = `<defs><filter id="${p}s" x="-10%" y="-20%" width="120%" height="150%"><feDropShadow dx="0" dy="3" stdDeviation="3" flood-color="#2a1f3a" flood-opacity=".35"/></filter>${bdGrain(`${p}g`, rnd, [1, 1, 1], 1.1, -0.62, 1.1)}</defs>`;
|
|
2203
|
+
s += `<rect width="${W}" height="${W}" fill="${pal[0]}"/>`;
|
|
2204
|
+
const sx = 60 + rnd() * 180;
|
|
2205
|
+
s += `<circle cx="${n(sx)}" cy="70" r="30" fill="${pal[1]}" filter="url(#${p}s)"/><circle cx="${n(sx)}" cy="70" r="20" fill="#fff" opacity=".35"/>`;
|
|
2206
|
+
for (let i = 0; i < 2; i++) {
|
|
2207
|
+
const x = 40 + rnd() * 220;
|
|
2208
|
+
const y = 30 + rnd() * 40;
|
|
2209
|
+
s += `<path d="M${n(x - 26)} ${n(y + 8)}a10 10 0 0 1 12 -12a14 14 0 0 1 26 -2a10 10 0 0 1 14 14Z" fill="#fff" filter="url(#${p}s)"/>`;
|
|
2210
|
+
}
|
|
2211
|
+
// capas de cerros de cartulina, del fondo al frente
|
|
2212
|
+
for (let i = 0; i < 5; i++) {
|
|
2213
|
+
const y = 112 + i * 30;
|
|
2214
|
+
const a = 26 - i * 3;
|
|
2215
|
+
let d = `M-10 ${W}V${n(y)}`;
|
|
2216
|
+
const k = 4;
|
|
2217
|
+
for (let j = 0; j < k; j++) {
|
|
2218
|
+
const x1 = -10 + ((W + 20) * (j + 0.5)) / k;
|
|
2219
|
+
const x2 = -10 + ((W + 20) * (j + 1)) / k;
|
|
2220
|
+
d += `Q${n(x1)} ${n(y + (j % 2 ? a : -a) * (0.6 + rnd() * 0.6))} ${n(x2)} ${n(y + (rnd() - 0.5) * 10)}`;
|
|
2221
|
+
}
|
|
2222
|
+
if (i === 4) d = `M-10 ${W}V${GROUND + 4}Q80 ${GROUND - 8} 150 ${GROUND}T${W + 10} ${GROUND - 2}V${W}Z`;
|
|
2223
|
+
else d += `V${W}Z`;
|
|
2224
|
+
s += `<path d="${d}" fill="${pal[i + 2]}" filter="url(#${p}s)"/>`;
|
|
2225
|
+
if (i === 2 || i === 3) {
|
|
2226
|
+
// pinos de papel en la capa
|
|
2227
|
+
for (let t = 0; t < 2; t++) {
|
|
2228
|
+
const tx = rnd() < 0.5 ? 20 + rnd() * 70 : 210 + rnd() * 70;
|
|
2229
|
+
const ty = y + 8;
|
|
2230
|
+
s += `<path d="M${n(tx)} ${n(ty - 34)}L${n(tx + 12)} ${n(ty)}H${n(tx - 12)}Z" fill="${pal[i + 3] || pal[6]}" filter="url(#${p}s)"/>`;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
2235
|
+
return s;
|
|
2236
|
+
},
|
|
2237
|
+
|
|
2238
|
+
lowpoly(p, rnd) {
|
|
2239
|
+
const sky0 = '#3d5a80';
|
|
2240
|
+
const sky1 = '#f6bd60';
|
|
2241
|
+
const jit = (c) => mix(c, rnd() < 0.5 ? '#ffffff' : '#000000', rnd() * 0.07);
|
|
2242
|
+
let s = facets(rnd, 0, 0, W, 190, 6, 4, (x, y) => jit(mix(sky0, sky1, Math.min(1, y / 180))));
|
|
2243
|
+
// sol octogonal
|
|
2244
|
+
const sx = 60 + rnd() * 180;
|
|
2245
|
+
let sun = '';
|
|
2246
|
+
for (let i = 0; i < 8; i++) {
|
|
2247
|
+
const a0 = (i / 8) * Math.PI * 2;
|
|
2248
|
+
const a1 = ((i + 1) / 8) * Math.PI * 2;
|
|
2249
|
+
sun += `<path d="M${n(sx)} 64L${n(sx + Math.cos(a0) * 24)} ${n(64 + Math.sin(a0) * 24)}L${n(sx + Math.cos(a1) * 24)} ${n(64 + Math.sin(a1) * 24)}Z" fill="${i % 2 ? '#fff1c1' : '#ffe39a'}" stroke="${i % 2 ? '#fff1c1' : '#ffe39a'}" stroke-width=".5"/>`;
|
|
2250
|
+
}
|
|
2251
|
+
s += sun;
|
|
2252
|
+
// montañas facetadas: lado iluminado y lado en sombra, con nieve
|
|
2253
|
+
const mount = (px, py, half, base, lit, dark) => {
|
|
2254
|
+
const P = [px, py];
|
|
2255
|
+
const BL = [px - half, base];
|
|
2256
|
+
const BR = [px + half * (0.9 + rnd() * 0.3), base];
|
|
2257
|
+
const L1 = [px - half * 0.5 + (rnd() - 0.5) * 10, (py + base) / 2 + (rnd() - 0.5) * 10];
|
|
2258
|
+
const R1 = [px + half * 0.5 + (rnd() - 0.5) * 10, (py + base) / 2 + (rnd() - 0.5) * 10];
|
|
2259
|
+
const C = [px + (rnd() - 0.5) * half * 0.3, base - (base - py) * 0.3];
|
|
2260
|
+
const T = (a, b, c, col) => `<path d="M${n(a[0])} ${n(a[1])}L${n(b[0])} ${n(b[1])}L${n(c[0])} ${n(c[1])}Z" fill="${col}" stroke="${col}" stroke-width=".6" stroke-linejoin="round"/>`;
|
|
2261
|
+
const at = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
2262
|
+
return T(P, L1, C, lit) + T(L1, BL, C, mix(lit, dark, 0.3)) + T(P, C, R1, mix(lit, dark, 0.6)) + T(R1, C, BR, dark) + T(BL, BR, C, mix(lit, dark, 0.45))
|
|
2263
|
+
+ T(P, at(P, L1, 0.38), at(P, C, 0.3), '#f4f6fb') + T(P, at(P, C, 0.3), at(P, R1, 0.38), '#c9d2e3');
|
|
2264
|
+
};
|
|
2265
|
+
s += mount(70 + rnd() * 30, 92, 90, 214, '#7d8fb3', '#46557a');
|
|
2266
|
+
s += mount(215 + rnd() * 30, 80, 100, 214, '#8a9cc0', '#4b5b82');
|
|
2267
|
+
s += mount(150, 126, 70, 214, '#6b7fa6', '#3a4870');
|
|
2268
|
+
// suelo de facetas verdes
|
|
2269
|
+
s += facets(rnd, 0, 206, W, W - 206, 6, 3, (x, y) => jit(mix('#8cc084', '#4f7d4a', (y - 206) / 94)));
|
|
2270
|
+
// pinos de dos caras
|
|
2271
|
+
for (let i = 0; i < 4; i++) {
|
|
2272
|
+
const tx = i < 2 ? 18 + i * 40 + rnd() * 14 : 214 + (i - 2) * 40 + rnd() * 14;
|
|
2273
|
+
const ty = GROUND - 8 + rnd() * 10;
|
|
2274
|
+
const h = 44 + rnd() * 20;
|
|
2275
|
+
s += `<path d="M${n(tx)} ${n(ty - h)}L${n(tx)} ${n(ty)}L${n(tx - 14)} ${n(ty)}Z" fill="#3f7a4f"/><path d="M${n(tx)} ${n(ty - h)}L${n(tx + 14)} ${n(ty)}L${n(tx)} ${n(ty)}Z" fill="#2a5a3a"/><path d="M${n(tx - 2)} ${n(ty)}h4v8h-4Z" fill="#5a3a2a"/>`;
|
|
2276
|
+
}
|
|
2277
|
+
return s;
|
|
2278
|
+
},
|
|
2279
|
+
|
|
2280
|
+
marble(p, rnd) {
|
|
2281
|
+
let s = `<defs><linearGradient id="${p}wl" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#cfc6b6"/><stop offset="1" stop-color="#e4ddd0"/></linearGradient>`
|
|
2282
|
+
+ `<radialGradient id="${p}sp" cx=".5" cy="0" r="1"><stop offset="0" stop-color="#fffaf0" stop-opacity=".75"/><stop offset=".6" stop-color="#fffaf0" stop-opacity="0"/></radialGradient>`
|
|
2283
|
+
+ `<linearGradient id="${p}cl" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#d6d0c4"/><stop offset=".35" stop-color="#f6f2ea"/><stop offset="1" stop-color="#bfb7a8"/></linearGradient>`
|
|
2284
|
+
+ `<filter id="${p}v" x="0" y="0" width="100%" height="100%"><feTurbulence type="turbulence" baseFrequency=".012 .04" numOctaves="4" seed="${Math.floor(rnd() * 900)}"/><feColorMatrix values="0 0 0 0 .42 0 0 0 0 .4 0 0 0 0 .38 -5 0 0 0 .55"/><feComposite in2="SourceGraphic" operator="in"/></filter></defs>`;
|
|
2285
|
+
s += `<rect width="${W}" height="${W}" fill="url(#${p}wl)"/>`;
|
|
2286
|
+
// hornacina en arco detrás del ot
|
|
2287
|
+
s += `<path d="M76 ${GROUND}V96A74 74 0 0 1 224 96V${GROUND}Z" fill="#bdb3a2"/><path d="M84 ${GROUND}V98A66 66 0 0 1 216 98V${GROUND}Z" fill="#c9c0b0"/>`;
|
|
2288
|
+
s += `<path d="M76 96A74 74 0 0 1 224 96" fill="none" stroke="#ece6da" stroke-width="3"/>`;
|
|
2289
|
+
// columnas estriadas con capitel jónico y basa
|
|
2290
|
+
const column = (x) => {
|
|
2291
|
+
let f = '';
|
|
2292
|
+
for (let i = 1; i < 5; i++) f += `M${x + i * 7} 46V${GROUND + 14}`;
|
|
2293
|
+
return `<rect x="${x}" y="46" width="35" height="${GROUND - 32}" fill="url(#${p}cl)"/><path d="${f}" stroke="#a89f90" stroke-width="1.2"/>`
|
|
2294
|
+
+ `<rect x="${x - 6}" y="34" width="47" height="12" fill="#ece6da"/><circle cx="${x - 3}" cy="44" r="7" fill="#ece6da" stroke="#a89f90"/><circle cx="${x + 38}" cy="44" r="7" fill="#ece6da" stroke="#a89f90"/><circle cx="${x - 3}" cy="44" r="2.5" fill="#a89f90"/><circle cx="${x + 38}" cy="44" r="2.5" fill="#a89f90"/><rect x="${x - 8}" y="26" width="51" height="8" fill="#f2ede4"/>`
|
|
2295
|
+
+ `<rect x="${x - 6}" y="${GROUND + 14}" width="47" height="8" fill="#ece6da"/><rect x="${x - 9}" y="${GROUND + 22}" width="53" height="6" fill="#d6cfc2"/>`;
|
|
2296
|
+
};
|
|
2297
|
+
s += column(16) + column(249);
|
|
2298
|
+
// foco cenital
|
|
2299
|
+
s += `<path d="M110 0H190L250 ${GROUND}H50Z" fill="url(#${p}sp)"/>`;
|
|
2300
|
+
// suelo de damero en perspectiva
|
|
2301
|
+
s += `<rect y="${GROUND + 28}" width="${W}" height="${W - GROUND - 28}" fill="#e8e2d6"/>`;
|
|
2302
|
+
let ck = '';
|
|
2303
|
+
for (let i = -6; i < 6; i++) ck += `M${150 + i * 30} ${GROUND + 28}L${150 + i * 46} ${W}`;
|
|
2304
|
+
s += `<path d="${ck}M0 ${GROUND + 40}H${W}" stroke="#8a8274" stroke-width="1" opacity=".5"/>`;
|
|
2305
|
+
// pedestal con placa
|
|
2306
|
+
s += `<rect x="72" y="${GROUND}" width="156" height="9" fill="#f4f0e8"/><rect x="82" y="${GROUND + 9}" width="136" height="${W - GROUND - 9}" fill="#e6e0d4"/><rect x="82" y="${GROUND + 9}" width="136" height="4" fill="#cfc7b8"/>`;
|
|
2307
|
+
s += `<rect x="72" y="${GROUND}" width="156" height="${W - GROUND}" filter="url(#${p}v)" opacity=".8"/>`;
|
|
2308
|
+
s += `<rect x="122" y="${GROUND + 24}" width="56" height="20" rx="1.5" fill="#b8923e"/><rect x="124" y="${GROUND + 26}" width="52" height="16" fill="none" stroke="#7a5e22" stroke-width=".8"/>`;
|
|
2309
|
+
s += `<text x="150" y="${GROUND + 37}" text-anchor="middle" font-family="Georgia,serif" font-size="8" letter-spacing="1" fill="#4a3810">MMXXVI</text>`;
|
|
2310
|
+
s += `<rect x="16" y="46" width="35" height="${GROUND - 32}" filter="url(#${p}v)" opacity=".6"/><rect x="249" y="46" width="35" height="${GROUND - 32}" filter="url(#${p}v)" opacity=".6"/>`;
|
|
2311
|
+
return s;
|
|
2312
|
+
},
|
|
2313
|
+
|
|
2314
|
+
candy(p, rnd) {
|
|
2315
|
+
const sprinkle = ['#ff4f8b', '#ffd23f', '#3ec1d3', '#7ed957', '#a77dff', '#ffffff', '#ff8c42'];
|
|
2316
|
+
let s = `<defs><pattern id="${p}st" width="22" height="22" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="22" height="22" fill="#ffd6e8"/><rect width="9" height="22" fill="#ffc2dc"/></pattern>`
|
|
2317
|
+
+ `<radialGradient id="${p}gl" cx=".35" cy=".3" r=".7"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/></radialGradient></defs>`;
|
|
2318
|
+
s += `<rect width="${W}" height="${W}" fill="url(#${p}st)"/>`;
|
|
2319
|
+
// chupetines espirales
|
|
2320
|
+
const lolly = (x, y, r, c) => {
|
|
2321
|
+
let sp = `M${x} ${y}`;
|
|
2322
|
+
for (let a = 0; a < Math.PI * 6; a += 0.3) sp += `L${n(x + Math.cos(a) * (a / (Math.PI * 6)) * r)} ${n(y + Math.sin(a) * (a / (Math.PI * 6)) * r)}`;
|
|
2323
|
+
return `<rect x="${x - 3}" y="${y}" width="6" height="${GROUND - y}" rx="3" fill="#fff" stroke="#e8b8cc" stroke-width="1"/><circle cx="${x}" cy="${y}" r="${r}" fill="${c}"/>`
|
|
2324
|
+
+ `<path d="${sp}" fill="none" stroke="#fff" stroke-width="${n(r * 0.16)}" stroke-linecap="round"/><circle cx="${x}" cy="${y}" r="${r}" fill="url(#${p}gl)"/>`;
|
|
2325
|
+
};
|
|
2326
|
+
const left = rnd() < 0.5;
|
|
2327
|
+
s += lolly(left ? 48 : 252, 92 + rnd() * 20, 32, '#ff6fa8');
|
|
2328
|
+
s += lolly(left ? 256 : 44, 130 + rnd() * 20, 24, '#7fd3e8');
|
|
2329
|
+
// caramelos envueltos flotando
|
|
2330
|
+
for (let i = 0; i < 2; i++) {
|
|
2331
|
+
const x = 90 + i * 120 + rnd() * 20;
|
|
2332
|
+
const y = 30 + rnd() * 30;
|
|
2333
|
+
const c = sprinkle[Math.floor(rnd() * 5)];
|
|
2334
|
+
s += `<g transform="rotate(${Math.floor(rnd() * 60 - 30)} ${n(x)} ${n(y)})"><path d="M${n(x - 12)} ${n(y)}l-10 -8v16Z M${n(x + 12)} ${n(y)}l10 -8v16Z" fill="${c}" opacity=".8"/><ellipse cx="${n(x)}" cy="${n(y)}" rx="13" ry="9" fill="${c}"/><ellipse cx="${n(x - 4)}" cy="${n(y - 3)}" rx="5" ry="2.5" fill="#fff" opacity=".6"/></g>`;
|
|
2335
|
+
}
|
|
2336
|
+
// pastel: bizcocho de chocolate con glaseado que chorrea
|
|
2337
|
+
s += `<rect y="${GROUND + 10}" width="${W}" height="${W - GROUND - 10}" fill="#8b5a3c"/><rect y="${GROUND + 40}" width="${W}" height="6" fill="#ffe0ec"/>`;
|
|
2338
|
+
let ic = `M0 ${GROUND - 4}H${W}V${GROUND + 12}`;
|
|
2339
|
+
for (let x = W; x > 0; x -= 20 + rnd() * 16) {
|
|
2340
|
+
const h = 6 + rnd() * 22;
|
|
2341
|
+
ic += `L${n(x - 4)} ${GROUND + 12}V${n(GROUND + 12 + h)}a5 5 0 0 1 -10 0V${GROUND + 12}`;
|
|
2342
|
+
}
|
|
2343
|
+
s += `<path d="${ic}L0 ${GROUND + 12}Z" fill="#ff9ec7"/><path d="M0 ${GROUND - 2}H${W}" stroke="#ffd0e4" stroke-width="3"/>`;
|
|
2344
|
+
// grageas en el aire y sobre el glaseado
|
|
2345
|
+
let sp = '';
|
|
2346
|
+
for (let i = 0; i < 70; i++) {
|
|
2347
|
+
const x = rnd() * W;
|
|
2348
|
+
const y = i < 40 ? rnd() * (GROUND - 10) : GROUND - 4 + rnd() * 14;
|
|
2349
|
+
sp += `<rect x="${n(x)}" y="${n(y)}" width="7" height="2.6" rx="1.3" fill="${sprinkle[Math.floor(rnd() * sprinkle.length)]}" transform="rotate(${Math.floor(rnd() * 180)} ${n(x)} ${n(y)})"/>`;
|
|
2350
|
+
}
|
|
2351
|
+
s += sp;
|
|
2352
|
+
return s;
|
|
2353
|
+
},
|
|
2354
|
+
|
|
2355
|
+
holo(p, rnd) {
|
|
2356
|
+
const rb = ['#ff8fd0', '#ffe08a', '#8affc0', '#8ad0ff', '#b98aff'];
|
|
2357
|
+
let s = `<defs><linearGradient id="${p}bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#140a2e"/><stop offset=".55" stop-color="#2a1458"/><stop offset="1" stop-color="#0d1a3a"/></linearGradient>`
|
|
2358
|
+
+ `<linearGradient id="${p}rb" x1="0" y1="0" x2="1" y2="0">${rb.map((c, i) => `<stop offset="${n(i / (rb.length - 1))}" stop-color="${c}"/>`).join('')}</linearGradient>`
|
|
2359
|
+
+ `<linearGradient id="${p}fl" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#0d0820" stop-opacity=".85"/></linearGradient>`
|
|
2360
|
+
+ `<filter id="${p}gw" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur stdDeviation="1.6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>`;
|
|
2361
|
+
s += `<rect width="${W}" height="${W}" fill="url(#${p}bg)"/>`;
|
|
2362
|
+
// haces tornasolados en diagonal
|
|
2363
|
+
for (let i = 0; i < 3; i++) {
|
|
2364
|
+
const x = -60 + i * 120 + rnd() * 40;
|
|
2365
|
+
s += `<path d="M${n(x)} 0h${n(30 + rnd() * 30)}l120 ${GROUND}h${n(-40 - rnd() * 30)}Z" fill="url(#${p}rb)" opacity=".13"/>`;
|
|
2366
|
+
}
|
|
2367
|
+
// prismas flotando
|
|
2368
|
+
for (let i = 0; i < 3; i++) {
|
|
2369
|
+
const x = i === 1 ? 150 + (rnd() - 0.5) * 60 : i ? 250 + rnd() * 20 : 30 + rnd() * 20;
|
|
2370
|
+
const y = i === 1 ? 30 + rnd() * 10 : 90 + rnd() * 70;
|
|
2371
|
+
const r = 10 + rnd() * 8;
|
|
2372
|
+
s += `<path d="M${n(x)} ${n(y - r)}L${n(x + r * 0.7)} ${n(y)}L${n(x)} ${n(y + r * 1.2)}L${n(x - r * 0.7)} ${n(y)}Z" fill="url(#${p}rb)" opacity=".85"/><path d="M${n(x)} ${n(y - r)}L${n(x + r * 0.7)} ${n(y)}L${n(x)} ${n(y + r * 1.2)}Z" fill="#fff" opacity=".25"/>`;
|
|
2373
|
+
}
|
|
2374
|
+
// piso tornasol con rejilla en perspectiva
|
|
2375
|
+
s += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="url(#${p}rb)" opacity=".75"/><rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="url(#${p}fl)"/>`;
|
|
2376
|
+
let grid = '';
|
|
2377
|
+
for (let i = -8; i <= 8; i++) grid += `M${150 + i * 18} ${GROUND}L${150 + i * 60} ${W}`;
|
|
2378
|
+
for (let j = 1; j < 6; j++) grid += `M0 ${n(GROUND + (W - GROUND) * (j / 6) ** 1.6)}H${W}`;
|
|
2379
|
+
s += `<path d="${grid}" stroke="#fff" stroke-width=".7" opacity=".35"/><path d="M0 ${GROUND}H${W}" stroke="#fff" stroke-width="1.5" opacity=".8"/>`;
|
|
2380
|
+
// purpurina y destellos
|
|
2381
|
+
let gl = '';
|
|
2382
|
+
for (let i = 0; i < 120; i++) gl += `<circle cx="${n(rnd() * W)}" cy="${n(rnd() * GROUND)}" r="${n(0.4 + rnd() * 1.1)}" fill="${rb[Math.floor(rnd() * rb.length)]}" opacity="${n(0.4 + rnd() * 0.6)}"/>`;
|
|
2383
|
+
s += gl;
|
|
2384
|
+
let sp = '';
|
|
2385
|
+
for (let i = 0; i < 9; i++) {
|
|
2386
|
+
const x = rnd() * W;
|
|
2387
|
+
const y = rnd() * (GROUND - 20);
|
|
2388
|
+
const r = 3 + rnd() * 6;
|
|
2389
|
+
sp += `M${n(x)} ${n(y - r)}Q${n(x)} ${n(y)} ${n(x + r)} ${n(y)}Q${n(x)} ${n(y)} ${n(x)} ${n(y + r)}Q${n(x)} ${n(y)} ${n(x - r)} ${n(y)}Q${n(x)} ${n(y)} ${n(x)} ${n(y - r)}Z`;
|
|
2390
|
+
}
|
|
2391
|
+
s += `<path d="${sp}" fill="#fff" filter="url(#${p}gw)"/>`;
|
|
2392
|
+
return s;
|
|
2393
|
+
},
|
|
2394
|
+
|
|
2395
|
+
sumie(p, rnd) {
|
|
2396
|
+
const ink = '#1d1b19';
|
|
2397
|
+
let s = `<defs>${bdGrain(`${p}g`, rnd, [0.55, 0.5, 0.42], 1.3, -0.74, 0.6)}<filter id="${p}b" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="2.5"/></filter>`
|
|
2398
|
+
+ `<filter id="${p}r" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency=".05" numOctaves="2" seed="${Math.floor(rnd() * 900)}"/><feDisplacementMap in="SourceGraphic" scale="5" xChannelSelector="R" yChannelSelector="G"/></filter>`
|
|
2399
|
+
+ `<linearGradient id="${p}m" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="${ink}" stop-opacity=".45"/><stop offset="1" stop-color="${ink}" stop-opacity="0"/></linearGradient>`
|
|
2400
|
+
+ `<linearGradient id="${p}m2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="${ink}" stop-opacity=".2"/><stop offset="1" stop-color="${ink}" stop-opacity="0"/></linearGradient></defs>`;
|
|
2401
|
+
s += `<rect width="${W}" height="${W}" fill="#f3eee2"/>`;
|
|
2402
|
+
// montañas en aguada que se pierden en la niebla
|
|
2403
|
+
const range = (base, amp, fill) => {
|
|
2404
|
+
let d = `M-10 ${base + 40}`;
|
|
2405
|
+
for (let x = -10; x <= W + 10; x += 25) d += `L${x} ${n(base - amp * (0.3 + rnd() * 0.7) * (x % 50 ? 0.6 : 1))}`;
|
|
2406
|
+
return `<path d="${d}L${W + 10} ${base + 40}Z" fill="url(#${fill})" filter="url(#${p}b)"/>`;
|
|
2407
|
+
};
|
|
2408
|
+
s += range(120, 50, `${p}m2`) + range(170, 60, `${p}m`);
|
|
2409
|
+
// ensō detrás del ot
|
|
2410
|
+
const a0 = rnd() * Math.PI * 2;
|
|
2411
|
+
const R = 98;
|
|
2412
|
+
const end = a0 + Math.PI * 1.82;
|
|
2413
|
+
const pt = (a) => `${n(150 + Math.cos(a) * R)} ${n(140 + Math.sin(a) * R)}`;
|
|
2414
|
+
s += `<g filter="url(#${p}r)"><path d="M${pt(a0)}A${R} ${R} 0 1 1 ${pt(end)}" fill="none" stroke="${ink}" stroke-width="11" stroke-linecap="round" opacity=".82"/>`
|
|
2415
|
+
+ `<path d="M${pt(a0 + 0.3)}A${R + 4} ${R + 4} 0 0 1 ${pt(a0 + 1.6)}" fill="none" stroke="#f3eee2" stroke-width="1.6" stroke-dasharray="14 6 30 4" opacity=".8"/></g>`;
|
|
2416
|
+
// bambú a un lado
|
|
2417
|
+
const bx = rnd() < 0.5 ? 30 : 262;
|
|
2418
|
+
let bam = '';
|
|
2419
|
+
for (let k = 0; k < 2; k++) {
|
|
2420
|
+
const x = bx + k * 14;
|
|
2421
|
+
for (let y = 10 + k * 30; y < GROUND; y += 34) bam += `<rect x="${x - 4}" y="${y}" width="8" height="31" rx="2"/>`;
|
|
2422
|
+
}
|
|
2423
|
+
let lv = '';
|
|
2424
|
+
for (let i = 0; i < 7; i++) {
|
|
2425
|
+
const x = bx + (rnd() - 0.3) * 20;
|
|
2426
|
+
const y = 30 + rnd() * 140;
|
|
2427
|
+
const dir = rnd() < 0.5 ? -1 : 1;
|
|
2428
|
+
lv += `<path d="M${n(x)} ${n(y)}q${n(dir * 14)} ${n(-4)} ${n(dir * 30)} ${n(6 + rnd() * 6)}q${n(-dir * 14)} ${n(2)} ${n(-dir * 30)} ${n(-6 - rnd() * 6)}Z"/>`;
|
|
2429
|
+
}
|
|
2430
|
+
s += `<g fill="${ink}" opacity=".78" filter="url(#${p}r)">${bam}${lv}</g>`;
|
|
2431
|
+
// pincelada del suelo y caligrafía vertical
|
|
2432
|
+
s += `<path d="M20 ${GROUND + 6}C80 ${GROUND - 4} 200 ${GROUND - 2} 286 ${GROUND + 4}L282 ${GROUND + 10}C200 ${GROUND + 6} 100 ${GROUND + 12} 24 ${GROUND + 12}Z" fill="${ink}" opacity=".7" filter="url(#${p}r)"/>`;
|
|
2433
|
+
const cx = bx < 150 ? 270 : 26;
|
|
2434
|
+
let cal = '';
|
|
2435
|
+
for (let i = 0; i < 4; i++) {
|
|
2436
|
+
const y = 26 + i * 22;
|
|
2437
|
+
cal += `M${cx - 6} ${y}h12M${cx} ${y - 5}v13M${cx - 5} ${y + 6}l${n(4 + rnd() * 4)} ${n(3 + rnd() * 3)}`;
|
|
2438
|
+
}
|
|
2439
|
+
s += `<path d="${cal}" stroke="${ink}" stroke-width="2.4" stroke-linecap="round" fill="none" opacity=".75"/>`;
|
|
2440
|
+
// sello rojo
|
|
2441
|
+
s += `<g transform="rotate(-4 ${cx} 128)"><rect x="${cx - 10}" y="118" width="20" height="22" rx="2" fill="#b8352a"/><path d="M${cx - 6} 123h12M${cx} 123v13M${cx - 6} 129h12M${cx - 5} 136h10" stroke="#f3eee2" stroke-width="1.6" fill="none"/></g>`;
|
|
2442
|
+
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
2443
|
+
return s;
|
|
2444
|
+
},
|
|
2445
|
+
|
|
2446
|
+
tarot(p, rnd) {
|
|
2447
|
+
const navy = '#1c2a5a';
|
|
2448
|
+
const gold = '#c9a23a';
|
|
2449
|
+
const cream = '#efe3c4';
|
|
2450
|
+
const ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI'];
|
|
2451
|
+
let s = `<defs>${bdGrain(`${p}g`, rnd, [0.35, 0.25, 0.1], 1.2, -0.66)}</defs>`;
|
|
2452
|
+
s += `<rect width="${W}" height="${W}" fill="${cream}"/><rect x="12" y="12" width="${W - 24}" height="${W - 24}" rx="6" fill="${navy}"/>`;
|
|
2453
|
+
// estrellas
|
|
2454
|
+
let st = '';
|
|
2455
|
+
for (let i = 0; i < 40; i++) st += `<circle cx="${n(18 + rnd() * (W - 36))}" cy="${n(46 + rnd() * 180)}" r="${n(0.5 + rnd() * 0.9)}"/>`;
|
|
2456
|
+
s += `<g fill="${cream}" opacity=".8">${st}</g>`;
|
|
2457
|
+
let big = '';
|
|
2458
|
+
for (let i = 0; i < 7; i++) big += star5(24 + rnd() * (W - 48), 52 + rnd() * 150, 3 + rnd() * 3);
|
|
2459
|
+
s += `<path d="${big}" fill="${gold}"/>`;
|
|
2460
|
+
// anillos radiantes detrás del ot
|
|
2461
|
+
s += `<g fill="none" stroke="${gold}" opacity=".35"><circle cx="150" cy="150" r="74" stroke-width="1.2"/><circle cx="150" cy="150" r="86" stroke-width=".8" stroke-dasharray="2 5"/><circle cx="150" cy="150" r="100" stroke-width=".8"/></g>`;
|
|
2462
|
+
// luna creciente y sol con cara
|
|
2463
|
+
const left = rnd() < 0.5;
|
|
2464
|
+
const mx = left ? 58 : 242;
|
|
2465
|
+
const sx = left ? 242 : 58;
|
|
2466
|
+
s += `<path d="M${mx} 52A24 24 0 1 0 ${mx} 100A19 19 0 1 1 ${mx} 52Z" fill="${gold}" transform="rotate(${left ? -20 : 20} ${mx} 76)"/>`;
|
|
2467
|
+
let rays = '';
|
|
2468
|
+
for (let i = 0; i < 16; i++) {
|
|
2469
|
+
const a = (i / 16) * Math.PI * 2;
|
|
2470
|
+
const r1 = i % 2 ? 30 : 36;
|
|
2471
|
+
rays += `M${n(sx + Math.cos(a - 0.12) * 19)} ${n(76 + Math.sin(a - 0.12) * 19)}L${n(sx + Math.cos(a) * r1)} ${n(76 + Math.sin(a) * r1)}L${n(sx + Math.cos(a + 0.12) * 19)} ${n(76 + Math.sin(a + 0.12) * 19)}Z`;
|
|
2472
|
+
}
|
|
2473
|
+
s += `<path d="${rays}" fill="${gold}"/><circle cx="${sx}" cy="76" r="18" fill="${gold}" stroke="${navy}" stroke-width="1"/>`;
|
|
2474
|
+
s += `<circle cx="${sx - 6}" cy="73" r="1.8" fill="${navy}"/><circle cx="${sx + 6}" cy="73" r="1.8" fill="${navy}"/><path d="M${sx - 6} 82Q${sx} 87 ${sx + 6} 82" fill="none" stroke="${navy}" stroke-width="1.4" stroke-linecap="round"/>`;
|
|
2475
|
+
// tierra y agua estilizadas
|
|
2476
|
+
s += `<path d="M12 ${GROUND}Q80 ${GROUND - 16} 150 ${GROUND}T${W - 12} ${GROUND - 4}V${W - 40}H12Z" fill="#2f7f7a"/><path d="M12 ${GROUND}Q80 ${GROUND - 16} 150 ${GROUND}T${W - 12} ${GROUND - 4}" fill="none" stroke="${gold}" stroke-width="1.6"/>`;
|
|
2477
|
+
let wv = '';
|
|
2478
|
+
for (let y = GROUND + 12; y < W - 42; y += 8) wv += `M20 ${y}q10 -4 20 0t20 0t20 0t20 0t20 0t20 0t20 0t20 0t20 0t20 0t20 0t20 0t20 0`;
|
|
2479
|
+
s += `<path d="${wv}" fill="none" stroke="${cream}" stroke-width="1" opacity=".35"/>`;
|
|
2480
|
+
// filete dorado doble, cartelas con número y ornamento
|
|
2481
|
+
s += `<rect x="18" y="18" width="${W - 36}" height="${W - 36}" rx="4" fill="none" stroke="${gold}" stroke-width="2.4"/><rect x="23" y="23" width="${W - 46}" height="${W - 46}" rx="3" fill="none" stroke="${gold}" stroke-width=".8"/>`;
|
|
2482
|
+
s += `<rect x="110" y="16" width="80" height="22" rx="2" fill="${cream}" stroke="${gold}" stroke-width="1.6"/>`;
|
|
2483
|
+
s += `<text x="150" y="33" text-anchor="middle" font-family="Georgia,'Times New Roman',serif" font-weight="bold" font-size="15" letter-spacing="2" fill="${navy}">${ROMAN[Math.floor(rnd() * ROMAN.length)]}</text>`;
|
|
2484
|
+
s += `<rect x="70" y="${W - 40}" width="160" height="22" rx="2" fill="${cream}" stroke="${gold}" stroke-width="1.6"/><path d="${star5(150, W - 29, 5)}${star5(120, W - 29, 3)}${star5(180, W - 29, 3)}" fill="${navy}"/><path d="M84 ${W - 29}H108M192 ${W - 29}H216" stroke="${navy}" stroke-width="1"/>`;
|
|
2485
|
+
for (const [x, y] of [[18, 18], [W - 18, 18], [18, W - 18], [W - 18, W - 18]]) s += `<path d="M${x} ${y - 6}L${x + 6} ${y}L${x} ${y + 6}L${x - 6} ${y}Z" fill="${gold}"/>`;
|
|
2486
|
+
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
2487
|
+
return s;
|
|
2488
|
+
},
|
|
2489
|
+
|
|
2490
|
+
retro70(p, rnd) {
|
|
2491
|
+
const cols = ['#4a2c1e', '#8a4b2a', '#c94f2c', '#e8762c', '#f2b33d'];
|
|
2492
|
+
let s = `<defs>${bdGrain(`${p}g`, rnd, [0.35, 0.22, 0.1], 1.3, -0.7)}</defs>`;
|
|
2493
|
+
s += `<rect width="${W}" height="${W}" fill="#f6e8c8"/>`;
|
|
2494
|
+
// rayos de sol desde el horizonte
|
|
2495
|
+
let rays = '';
|
|
2496
|
+
const ox = 150;
|
|
2497
|
+
for (let i = 0; i < 24; i += 2) {
|
|
2498
|
+
const a0 = Math.PI + (i / 24) * Math.PI;
|
|
2499
|
+
const a1 = Math.PI + ((i + 1) / 24) * Math.PI;
|
|
2500
|
+
rays += `M${ox} ${GROUND}L${n(ox + Math.cos(a0) * 400)} ${n(GROUND + Math.sin(a0) * 400)}L${n(ox + Math.cos(a1) * 400)} ${n(GROUND + Math.sin(a1) * 400)}Z`;
|
|
2501
|
+
}
|
|
2502
|
+
s += `<path d="${rays}" fill="#f2c46d" opacity=".55"/>`;
|
|
2503
|
+
// arcoíris setentero
|
|
2504
|
+
cols.forEach((c, i) => {
|
|
2505
|
+
const r = 132 - i * 17;
|
|
2506
|
+
s += `<path d="M${ox - r} ${GROUND}A${r} ${r} 0 0 1 ${ox + r} ${GROUND}" fill="none" stroke="${c}" stroke-width="14"/>`;
|
|
2507
|
+
});
|
|
2508
|
+
s += `<path d="M${ox - 48} ${GROUND}A48 48 0 0 1 ${ox + 48} ${GROUND}Z" fill="#f6e8c8"/>`;
|
|
2509
|
+
// nubes redondas en dos tonos
|
|
2510
|
+
for (let i = 0; i < 2; i++) {
|
|
2511
|
+
const x = i ? 238 + rnd() * 30 : 34 + rnd() * 30;
|
|
2512
|
+
const y = 40 + rnd() * 40;
|
|
2513
|
+
s += `<path d="M${n(x - 26)} ${n(y + 8)}a9 9 0 0 1 10 -12a13 13 0 0 1 24 -2a9 9 0 0 1 18 14Z" fill="#fff8e8" stroke="#8a4b2a" stroke-width="2"/>`;
|
|
2514
|
+
}
|
|
2515
|
+
// suelo con ondas
|
|
2516
|
+
s += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="#8a4b2a"/>`;
|
|
2517
|
+
['#c94f2c', '#e8762c', '#f2b33d'].forEach((c, i) => {
|
|
2518
|
+
const y = GROUND + 12 + i * 12;
|
|
2519
|
+
s += `<path d="M-10 ${y}q20 -8 40 0t40 0t40 0t40 0t40 0t40 0t40 0t40 0" fill="none" stroke="${c}" stroke-width="5"/>`;
|
|
2520
|
+
});
|
|
2521
|
+
// margaritas
|
|
2522
|
+
const daisy = (x, y, r) => {
|
|
2523
|
+
let pe = '';
|
|
2524
|
+
for (let i = 0; i < 8; i++) pe += `<ellipse cx="${n(x)}" cy="${n(y - r)}" rx="${n(r * 0.42)}" ry="${n(r * 0.75)}" transform="rotate(${i * 45} ${n(x)} ${n(y)})"/>`;
|
|
2525
|
+
return `<path d="M${n(x)} ${n(y)}V${GROUND + 4}" stroke="#6b7a3a" stroke-width="3"/><g fill="#fffaf0" stroke="#4a2c1e" stroke-width="1.2">${pe}</g><circle cx="${n(x)}" cy="${n(y)}" r="${n(r * 0.45)}" fill="#f2b33d" stroke="#4a2c1e" stroke-width="1.2"/>`;
|
|
2526
|
+
};
|
|
2527
|
+
s += daisy(28 + rnd() * 12, GROUND - 28, 11) + daisy(64 + rnd() * 10, GROUND - 12, 8) + daisy(262 + rnd() * 12, GROUND - 32, 12) + daisy(228 + rnd() * 8, GROUND - 10, 7);
|
|
2528
|
+
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
2529
|
+
return s;
|
|
2530
|
+
},
|
|
2531
|
+
|
|
2532
|
+
mosaic(p, rnd) {
|
|
2533
|
+
const T = 12;
|
|
2534
|
+
const BAND = 18;
|
|
2535
|
+
const sx = 60 + rnd() * 180;
|
|
2536
|
+
const sy = 64;
|
|
2537
|
+
const col = (x, y) => {
|
|
2538
|
+
if ((x - sx) ** 2 + (y - sy) ** 2 < 26 ** 2) return (x - sx) ** 2 + (y - sy) ** 2 < 14 ** 2 ? '#e6c26a' : '#d98a3a';
|
|
2539
|
+
const hill = GROUND - 30 + Math.sin(x / 40 + sx) * 14;
|
|
2540
|
+
if (y > GROUND) return rnd() < 0.5 ? '#b5452e' : '#a03c2a';
|
|
2541
|
+
if (y > hill) return rnd() < 0.5 ? '#6a8a4a' : '#5a7a3e';
|
|
2542
|
+
return mix('#2c4a6e', '#8ab0c0', Math.min(1, y / 200));
|
|
2543
|
+
};
|
|
2544
|
+
let s = `<rect width="${W}" height="${W}" fill="#b8ad98"/>`;
|
|
2545
|
+
let tiles = '';
|
|
2546
|
+
for (let y = BAND; y < W - BAND; y += T) {
|
|
2547
|
+
for (let x = BAND; x < W - BAND; x += T) {
|
|
2548
|
+
const c = mix(col(x + T / 2, y + T / 2), rnd() < 0.5 ? '#ffffff' : '#000000', rnd() * 0.12);
|
|
2549
|
+
const j = () => (rnd() - 0.5) * 1.6;
|
|
2550
|
+
tiles += `<rect x="${n(x + 1 + j())}" y="${n(y + 1 + j())}" width="${n(T - 2 + j() * 0.5)}" height="${n(T - 2 + j() * 0.5)}" fill="${c}" transform="rotate(${n((rnd() - 0.5) * 6)} ${x + T / 2} ${y + T / 2})"/>`;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
s += tiles;
|
|
2554
|
+
// greca en la cenefa
|
|
2555
|
+
s += `<path d="M0 0H${W}V${W}H0ZM${BAND} ${BAND}V${W - BAND}H${W - BAND}V${BAND}Z" fill-rule="evenodd" fill="#efe4cc"/>`;
|
|
2556
|
+
const key = (x) => `M${x} 14V4H${x + 12}V10H${x + 6}V8`;
|
|
2557
|
+
let k = '';
|
|
2558
|
+
for (let x = 2; x < W - 10; x += 16) k += key(x);
|
|
2559
|
+
const band = `<path d="${k}" fill="none" stroke="#3a3530" stroke-width="2.2"/>`;
|
|
2560
|
+
s += band + `<g transform="translate(0 ${W - 18})">${band}</g><g transform="translate(18 0) rotate(90)">${band}</g><g transform="translate(${W} 0) rotate(90)">${band}</g>`;
|
|
2561
|
+
s += `<path d="M${BAND} ${BAND}H${W - BAND}V${W - BAND}H${BAND}Z" fill="none" stroke="#3a3530" stroke-width="2"/>`;
|
|
2562
|
+
return s;
|
|
2563
|
+
},
|
|
2564
|
+
|
|
2565
|
+
neonsign(p, rnd) {
|
|
2566
|
+
const tubes = ['#ff4fd8', '#3ef0ff', '#ffe14f', '#7dff6a', '#ff6a3d'];
|
|
2567
|
+
const t1 = tubes[Math.floor(rnd() * tubes.length)];
|
|
2568
|
+
const t2 = tubes.filter((c) => c !== t1)[Math.floor(rnd() * (tubes.length - 1))];
|
|
2569
|
+
let s = `<defs><pattern id="${p}br" width="32" height="18" patternUnits="userSpaceOnUse"><rect width="32" height="18" fill="#140c10"/><rect x="1" y="1" width="30" height="7.5" rx="1" fill="#2e1c22"/><rect x="-15" y="10" width="30" height="7.5" rx="1" fill="#2a191f"/><rect x="17" y="10" width="30" height="7.5" rx="1" fill="#33202a"/></pattern>`
|
|
2570
|
+
+ `<radialGradient id="${p}vg" cx=".5" cy=".35" r=".75"><stop offset="0" stop-color="${t1}" stop-opacity=".22"/><stop offset=".55" stop-color="#000" stop-opacity=".2"/><stop offset="1" stop-color="#000" stop-opacity=".7"/></radialGradient>`
|
|
2571
|
+
+ `<filter id="${p}n" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="2.5" result="a"/><feGaussianBlur stdDeviation="8" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="a"/><feMergeNode in="SourceGraphic"/></feMerge></filter>`
|
|
2572
|
+
+ `<filter id="${p}rf" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="5 2"/></filter></defs>`;
|
|
2573
|
+
s += `<rect width="${W}" height="${GROUND}" fill="url(#${p}br)"/><rect width="${W}" height="${GROUND}" fill="url(#${p}vg)"/>`;
|
|
2574
|
+
// letrero principal con su cable
|
|
2575
|
+
s += `<path d="M110 0V22M190 0V22" stroke="#0a0608" stroke-width="1.5"/>`;
|
|
2576
|
+
const font = 'font-family="\'Brush Script MT\',\'Segoe Script\',\'URW Chancery L\',cursive" font-style="italic" font-weight="bold" font-size="46" text-anchor="middle"';
|
|
2577
|
+
s += `<g filter="url(#${p}n)"><text x="150" y="66" ${font} fill="none" stroke="${t1}" stroke-width="3.2" stroke-linejoin="round">7ots</text></g>`;
|
|
2578
|
+
s += `<text x="150" y="66" ${font} fill="none" stroke="#fff" stroke-width="1" opacity=".75">7ots</text>`;
|
|
2579
|
+
// corazón y flecha de neón a los lados
|
|
2580
|
+
const left = rnd() < 0.5;
|
|
2581
|
+
const hx = left ? 46 : 254;
|
|
2582
|
+
const ax = left ? 254 : 46;
|
|
2583
|
+
s += `<g filter="url(#${p}n)" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M${hx} 138C${hx - 26} 120 ${hx - 16} 98 ${hx} 110C${hx + 16} 98 ${hx + 26} 120 ${hx} 138Z" stroke="${t2}" stroke-width="3"/>`
|
|
2584
|
+
+ `<path d="M${ax + (left ? 22 : -22)} 150H${ax - (left ? 18 : -18)}M${ax - (left ? 6 : -6)} 140L${ax - (left ? 18 : -18)} 150L${ax - (left ? 6 : -6)} 160" stroke="${t1}" stroke-width="3"/></g>`;
|
|
2585
|
+
s += `<path d="M${hx} 138C${hx - 26} 120 ${hx - 16} 98 ${hx} 110C${hx + 16} 98 ${hx + 26} 120 ${hx} 138Z" fill="none" stroke="#fff" stroke-width=".9" opacity=".7"/>`;
|
|
2586
|
+
// vereda mojada con reflejos
|
|
2587
|
+
s += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="#0c0a10"/><path d="M0 ${GROUND}H${W}" stroke="#2a2030" stroke-width="2"/>`;
|
|
2588
|
+
s += `<g filter="url(#${p}rf)" opacity=".5"><rect x="110" y="${GROUND + 8}" width="80" height="34" fill="${t1}"/><rect x="${hx - 12}" y="${GROUND + 10}" width="24" height="26" fill="${t2}"/><rect x="${ax - 14}" y="${GROUND + 14}" width="28" height="18" fill="${t1}"/></g>`;
|
|
2589
|
+
let wet = '';
|
|
2590
|
+
for (let i = 0; i < 10; i++) wet += `M${n(rnd() * W)} ${n(GROUND + 8 + rnd() * 50)}h${n(10 + rnd() * 30)}`;
|
|
2591
|
+
s += `<path d="${wet}" stroke="#fff" stroke-width="1" opacity=".15"/>`;
|
|
2592
|
+
return s;
|
|
2593
|
+
},
|
|
2594
|
+
|
|
2595
|
+
graffiti(p, rnd) {
|
|
2596
|
+
const cols = ['#ff2e63', '#08d9d6', '#f9ed32', '#7cfc00', '#ff8c00', '#9b5de5', '#00bbf9'];
|
|
2597
|
+
const pick = () => cols[Math.floor(rnd() * cols.length)];
|
|
2598
|
+
let s = `<defs>${bdGrain(`${p}g`, rnd, [0.3, 0.3, 0.3], 1.4, -0.72, 0.8)}<filter id="${p}sp" x="-20%" y="-20%" width="140%" height="140%"><feTurbulence type="fractalNoise" baseFrequency="1.2" numOctaves="1" seed="${Math.floor(rnd() * 900)}" result="n"/><feGaussianBlur in="SourceGraphic" stdDeviation="2.4" result="b"/><feColorMatrix in="n" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 0 0 0 -5.6" result="m"/><feComposite in="b" in2="m" operator="in" result="d"/><feMerge><feMergeNode in="d"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>`;
|
|
2599
|
+
// muro de hormigón en paneles
|
|
2600
|
+
s += `<rect width="${W}" height="${GROUND}" fill="#b9b5ad"/><path d="M100 0V${GROUND}M200 0V${GROUND}M0 120H${W}" stroke="#8f8b84" stroke-width="2"/>`;
|
|
2601
|
+
// pieza de fondo: burbujas superpuestas con contorno negro, brillo y chorreones
|
|
2602
|
+
const c1 = pick();
|
|
2603
|
+
const c2 = cols.filter((c) => c !== c1)[Math.floor(rnd() * (cols.length - 1))];
|
|
2604
|
+
let blobs = '';
|
|
2605
|
+
const B = [[96, 112, 46], [150, 92, 54], [206, 116, 44]];
|
|
2606
|
+
for (const [x, y, r] of B) blobs += `<circle cx="${x}" cy="${y}" r="${r}"/>`;
|
|
2607
|
+
let drips = '';
|
|
2608
|
+
for (let i = 0; i < 7; i++) {
|
|
2609
|
+
const x = 70 + rnd() * 160;
|
|
2610
|
+
const h = 20 + rnd() * 60;
|
|
2611
|
+
drips += `M${n(x)} 140V${n(140 + h)}`;
|
|
2612
|
+
}
|
|
2613
|
+
s += `<g filter="url(#${p}sp)"><g fill="#111" transform="translate(5 5)">${blobs}</g><g fill="${c1}" stroke="#111" stroke-width="5">${blobs}</g><g fill="${c1}">${blobs}</g>`
|
|
2614
|
+
+ `<path d="${drips}" stroke="${c1}" stroke-width="5" stroke-linecap="round"/>`
|
|
2615
|
+
+ `<circle cx="150" cy="92" r="30" fill="${c2}" opacity=".85"/><path d="M80 96Q84 78 100 74M136 62Q144 50 160 50" fill="none" stroke="#fff" stroke-width="5" stroke-linecap="round"/></g>`;
|
|
2616
|
+
// tags y una corona
|
|
2617
|
+
const tag = (x, y, c) => {
|
|
2618
|
+
let d = `M${n(x)} ${n(y)}`;
|
|
2619
|
+
for (let i = 0; i < 6; i++) d += `q${n(4 + rnd() * 8)} ${n(-12 - rnd() * 10)} ${n(10 + rnd() * 6)} ${n(-2 + rnd() * 4)}t${n(6 + rnd() * 6)} ${n(10 + rnd() * 6)}`;
|
|
2620
|
+
return `<path d="${d}" fill="none" stroke="${c}" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
2621
|
+
};
|
|
2622
|
+
s += tag(14, 40, '#111') + tag(196, 210, '#6a2c8c') + tag(16, 222, '#111');
|
|
2623
|
+
const kx = 238 + rnd() * 20;
|
|
2624
|
+
s += `<path d="M${n(kx - 20)} 50L${n(kx - 22)} 26L${n(kx - 10)} 38L${n(kx)} 20L${n(kx + 10)} 38L${n(kx + 22)} 26L${n(kx + 20)} 50Z" fill="none" stroke="${pick()}" stroke-width="3" stroke-linejoin="round"/>`;
|
|
2625
|
+
// salpicaduras
|
|
2626
|
+
let sp = '';
|
|
2627
|
+
for (let i = 0; i < 40; i++) sp += `<circle cx="${n(rnd() * W)}" cy="${n(rnd() * GROUND)}" r="${n(0.6 + rnd() * 1.8)}" fill="${pick()}" opacity=".8"/>`;
|
|
2628
|
+
s += sp;
|
|
2629
|
+
// asfalto con manchas de pintura y un aerosol tirado
|
|
2630
|
+
s += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="#4a4a4e"/><path d="M0 ${GROUND}H${W}" stroke="#2e2e32" stroke-width="3"/>`;
|
|
2631
|
+
for (let i = 0; i < 3; i++) s += `<ellipse cx="${n(30 + rnd() * 240)}" cy="${n(GROUND + 20 + rnd() * 30)}" rx="${n(8 + rnd() * 14)}" ry="${n(3 + rnd() * 4)}" fill="${pick()}" opacity=".85"/>`;
|
|
2632
|
+
const cx = rnd() < 0.5 ? 40 : 250;
|
|
2633
|
+
const cc = pick();
|
|
2634
|
+
s += `<g transform="rotate(-12 ${cx} ${GROUND + 20})"><rect x="${cx - 9}" y="${GROUND - 14}" width="18" height="36" rx="4" fill="${cc}" stroke="#111" stroke-width="2"/><rect x="${cx - 9}" y="${GROUND - 2}" width="18" height="8" fill="#fff"/><rect x="${cx - 6}" y="${GROUND - 22}" width="12" height="9" rx="3" fill="#ddd" stroke="#111" stroke-width="2"/><rect x="${cx - 2}" y="${GROUND - 26}" width="4" height="5" fill="#111"/></g>`;
|
|
2635
|
+
s += `<rect width="${W}" height="${W}" filter="url(#${p}g)"/>`;
|
|
2636
|
+
return s;
|
|
2637
|
+
},
|
|
2638
|
+
|
|
2639
|
+
thermal(p, rnd) {
|
|
2640
|
+
const ramp = ['#0a0520', '#2a0a6a', '#8a1a8a', '#e8401c', '#ffb000', '#fffbe0'];
|
|
2641
|
+
let s = `<defs><linearGradient id="${p}sk" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#07031a"/><stop offset="1" stop-color="#26105e"/></linearGradient>`
|
|
2642
|
+
+ `<linearGradient id="${p}sc" x1="0" y1="1" x2="0" y2="0">${ramp.map((c, i) => `<stop offset="${n(i / (ramp.length - 1))}" stop-color="${c}"/>`).join('')}</linearGradient>`
|
|
2643
|
+
+ `<radialGradient id="${p}hot"><stop offset="0" stop-color="#fffbe0"/><stop offset=".25" stop-color="#ffb000"/><stop offset=".55" stop-color="#e8401c" stop-opacity=".8"/><stop offset=".8" stop-color="#8a1a8a" stop-opacity=".4"/><stop offset="1" stop-color="#2a0a6a" stop-opacity="0"/></radialGradient>`
|
|
2644
|
+
+ `<filter id="${p}bl" x="-5%" y="-5%" width="110%" height="110%"><feGaussianBlur stdDeviation="1.4"/></filter>${bdGrain(`${p}g`, rnd, [1, 1, 1], 0.9, -0.52, 1.2)}</defs>`;
|
|
2645
|
+
let g = `<rect width="${W}" height="${W}" fill="url(#${p}sk)"/>`;
|
|
2646
|
+
// edificios fríos con alguna ventana tibia
|
|
2647
|
+
let bld = '';
|
|
2648
|
+
let win = '';
|
|
2649
|
+
for (let x = -10; x < W; x += 34 + rnd() * 20) {
|
|
2650
|
+
const w = 30 + rnd() * 26;
|
|
2651
|
+
const h = 60 + rnd() * 90;
|
|
2652
|
+
bld += `<rect x="${n(x)}" y="${n(GROUND - h)}" width="${n(w)}" height="${n(h)}"/>`;
|
|
2653
|
+
for (let wy = GROUND - h + 10; wy < GROUND - 14; wy += 16) for (let wx = x + 6; wx < x + w - 10; wx += 12) if (rnd() < 0.3) win += `<rect x="${n(wx)}" y="${n(wy)}" width="6" height="8" fill="${rnd() < 0.3 ? '#e8401c' : '#8a1a8a'}"/>`;
|
|
2654
|
+
}
|
|
2655
|
+
g += `<g fill="#1a0848">${bld}</g>${win}`;
|
|
2656
|
+
// farol caliente (a la izquierda: la escala va a la derecha)
|
|
2657
|
+
const lx = 44 + rnd() * 10;
|
|
2658
|
+
g += `<rect x="${n(lx - 2)}" y="116" width="4" height="${GROUND - 116}" fill="#2a0a6a"/><circle cx="${n(lx)}" cy="112" r="30" fill="url(#${p}hot)"/>`;
|
|
2659
|
+
// suelo frío
|
|
2660
|
+
g += `<rect y="${GROUND}" width="${W}" height="${W - GROUND}" fill="#140838"/><path d="M0 ${GROUND}H${W}" stroke="#2a0a6a" stroke-width="3"/>`;
|
|
2661
|
+
g += `<ellipse cx="150" cy="${GROUND + 6}" rx="56" ry="7" fill="#8a1a8a" opacity=".55"/>`;
|
|
2662
|
+
let s2 = `${s}<g filter="url(#${p}bl)">${g}</g><rect width="${W}" height="${W}" filter="url(#${p}g)" opacity=".5"/>`;
|
|
2663
|
+
// HUD: esquinas, mira, escala de color y lecturas
|
|
2664
|
+
const hud = '#f4f0ff';
|
|
2665
|
+
s2 += `<g fill="none" stroke="${hud}" stroke-width="2" opacity=".85"><path d="M26 44V26H44M${W - 44} 26H${W - 26}V44M26 ${W - 44}V${W - 26}H44M${W - 44} ${W - 26}H${W - 26}V${W - 44}"/><path d="M150 132V142M150 158V168M132 150H142M158 150H168" stroke-width="1.4"/></g>`;
|
|
2666
|
+
s2 += `<rect x="${W - 46}" y="66" width="9" height="140" fill="url(#${p}sc)" stroke="${hud}" stroke-width=".8"/>`;
|
|
2667
|
+
const tmax = (36 + rnd() * 2).toFixed(1);
|
|
2668
|
+
const tmin = (8 + rnd() * 6).toFixed(1);
|
|
2669
|
+
s2 += `<g font-family="monospace" fill="${hud}"><text x="${W - 42}" y="61" font-size="8" text-anchor="middle">${tmax}°</text><text x="${W - 42}" y="216" font-size="8" text-anchor="middle">${tmin}°</text>`
|
|
2670
|
+
+ `<text x="34" y="58" font-size="14" font-weight="bold">${(36.2 + rnd() * 0.8).toFixed(1)}°C</text><text x="34" y="70" font-size="7" opacity=".8">ε 0.95 ⌀ ${Math.floor(2 + rnd() * 3)}.${Math.floor(rnd() * 9)}m</text>`
|
|
2671
|
+
+ `<text x="34" y="${W - 34}" font-size="8"><tspan fill="#ff4a3a">●</tspan> REC</text></g>`;
|
|
2672
|
+
return s2;
|
|
2673
|
+
},
|
|
1398
2674
|
};
|
|
1399
2675
|
|
|
1400
2676
|
/**
|