git-hash-art 0.6.0 → 0.8.0
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/ALGORITHM.md +30 -3
- package/CHANGELOG.md +16 -0
- package/bin/generateExamples.js +6 -14
- package/dist/browser.js +1289 -125
- package/dist/browser.js.map +1 -1
- package/dist/main.js +1289 -125
- package/dist/main.js.map +1 -1
- package/dist/module.js +1289 -125
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/archetypes.ts +51 -0
- package/src/lib/canvas/colors.ts +155 -2
- package/src/lib/canvas/draw.ts +42 -9
- package/src/lib/canvas/shapes/affinity.ts +479 -0
- package/src/lib/canvas/shapes/index.ts +2 -0
- package/src/lib/canvas/shapes/procedural.ts +209 -0
- package/src/lib/render.ts +282 -136
package/dist/main.js
CHANGED
|
@@ -27,17 +27,22 @@ $parcel$export(module.exports, "DEFAULT_CONFIG", () => $93cf69256c93baa9$export$
|
|
|
27
27
|
* identically in Node (@napi-rs/canvas) and browsers.
|
|
28
28
|
*
|
|
29
29
|
* Generation pipeline:
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
30
|
+
* 0. Archetype selection + shape palette + color hierarchy
|
|
31
|
+
* 1. Background — style from archetype, gradient mesh for depth
|
|
32
|
+
* 1b. Layered background — archetype-coherent shapes
|
|
33
|
+
* 2. Composition mode + symmetry
|
|
34
|
+
* 3. Focal points + void zones + hero avoidance field
|
|
35
|
+
* 4. Flow field
|
|
36
|
+
* 4b. Hero shape
|
|
37
|
+
* 5. Shape layers — palette-driven selection, affinity-aware styles,
|
|
38
|
+
* size echo, tangent placement, atmospheric depth
|
|
37
39
|
* 5b. Recursive nesting
|
|
38
|
-
* 6. Flow
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* 6. Flow lines — variable color, branching, pressure simulation
|
|
41
|
+
* 6b. Symmetry mirroring
|
|
42
|
+
* 7. Noise texture
|
|
43
|
+
* 8. Vignette
|
|
44
|
+
* 9. Organic connecting curves
|
|
45
|
+
* 10. Post-processing — color grading, chromatic aberration, bloom
|
|
41
46
|
*/
|
|
42
47
|
// declare module 'color-scheme';
|
|
43
48
|
|
|
@@ -411,6 +416,67 @@ function $d016ad53434219a1$export$f2121afcad3d553f(hex, alpha) {
|
|
|
411
416
|
const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex);
|
|
412
417
|
return `rgba(${r},${g},${b},${alpha.toFixed(3)})`;
|
|
413
418
|
}
|
|
419
|
+
function $d016ad53434219a1$export$fabac4600b87056(colors, rng) {
|
|
420
|
+
if (colors.length < 3) return {
|
|
421
|
+
dominant: colors[0] || "#888888",
|
|
422
|
+
secondary: colors[1] || colors[0] || "#888888",
|
|
423
|
+
accent: colors[colors.length - 1] || "#888888",
|
|
424
|
+
all: colors
|
|
425
|
+
};
|
|
426
|
+
// Pick dominant as the color closest to the palette's average hue
|
|
427
|
+
const hsls = colors.map((c)=>$d016ad53434219a1$var$hexToHsl(c));
|
|
428
|
+
const avgHue = hsls.reduce((s, h)=>s + h[0], 0) / hsls.length;
|
|
429
|
+
let dominantIdx = 0;
|
|
430
|
+
let minDist = 360;
|
|
431
|
+
for(let i = 0; i < hsls.length; i++){
|
|
432
|
+
const d = Math.min(Math.abs(hsls[i][0] - avgHue), 360 - Math.abs(hsls[i][0] - avgHue));
|
|
433
|
+
if (d < minDist) {
|
|
434
|
+
minDist = d;
|
|
435
|
+
dominantIdx = i;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Accent is the color most distant from dominant in hue
|
|
439
|
+
let accentIdx = 0;
|
|
440
|
+
let maxDist = 0;
|
|
441
|
+
for(let i = 0; i < hsls.length; i++){
|
|
442
|
+
if (i === dominantIdx) continue;
|
|
443
|
+
const d = Math.min(Math.abs(hsls[i][0] - hsls[dominantIdx][0]), 360 - Math.abs(hsls[i][0] - hsls[dominantIdx][0]));
|
|
444
|
+
if (d > maxDist) {
|
|
445
|
+
maxDist = d;
|
|
446
|
+
accentIdx = i;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Secondary is the remaining color with highest saturation
|
|
450
|
+
let secondaryIdx = 0;
|
|
451
|
+
let maxSat = -1;
|
|
452
|
+
for(let i = 0; i < hsls.length; i++){
|
|
453
|
+
if (i === dominantIdx || i === accentIdx) continue;
|
|
454
|
+
if (hsls[i][1] > maxSat) {
|
|
455
|
+
maxSat = hsls[i][1];
|
|
456
|
+
secondaryIdx = i;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (secondaryIdx === dominantIdx) secondaryIdx = accentIdx === 0 ? 1 : 0;
|
|
460
|
+
return {
|
|
461
|
+
dominant: colors[dominantIdx],
|
|
462
|
+
secondary: colors[secondaryIdx],
|
|
463
|
+
accent: colors[accentIdx],
|
|
464
|
+
all: colors
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function $d016ad53434219a1$export$b49f62f0a99da0e8(hierarchy, rng) {
|
|
468
|
+
const roll = rng();
|
|
469
|
+
if (roll < 0.60) return hierarchy.dominant;
|
|
470
|
+
if (roll < 0.85) return hierarchy.secondary;
|
|
471
|
+
return hierarchy.accent;
|
|
472
|
+
}
|
|
473
|
+
function $d016ad53434219a1$export$18a34c25ea7e724b(hex, rng, hueAmount = 8, slAmount = 0.06) {
|
|
474
|
+
const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
|
|
475
|
+
const newH = (h + (rng() - 0.5) * hueAmount * 2 + 360) % 360;
|
|
476
|
+
const newS = Math.max(0, Math.min(1, s + (rng() - 0.5) * slAmount * 2));
|
|
477
|
+
const newL = Math.max(0, Math.min(1, l + (rng() - 0.5) * slAmount * 2));
|
|
478
|
+
return $d016ad53434219a1$var$hslToHex(newH, newS, newL);
|
|
479
|
+
}
|
|
414
480
|
function $d016ad53434219a1$export$59539d800dbe6858(hex, rng, amount = 0.1) {
|
|
415
481
|
const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex);
|
|
416
482
|
const jit = ()=>(rng() - 0.5) * 2 * amount * 255;
|
|
@@ -426,6 +492,55 @@ function $d016ad53434219a1$export$51ea55f869b7e0d3(hex, target, amount) {
|
|
|
426
492
|
const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
|
|
427
493
|
return $d016ad53434219a1$var$hslToHex($d016ad53434219a1$var$shiftHueToward(h, target, amount), s, l);
|
|
428
494
|
}
|
|
495
|
+
function $d016ad53434219a1$export$5c6e3c2b59b7fbbe(hex) {
|
|
496
|
+
const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex).map((c)=>{
|
|
497
|
+
const s = c / 255;
|
|
498
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
499
|
+
});
|
|
500
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
501
|
+
}
|
|
502
|
+
function $d016ad53434219a1$export$90ad0e6170cf6af5(fgHex, bgLuminance, minContrast = 0.15) {
|
|
503
|
+
const fgLum = $d016ad53434219a1$export$5c6e3c2b59b7fbbe(fgHex);
|
|
504
|
+
const diff = Math.abs(fgLum - bgLuminance);
|
|
505
|
+
if (diff >= minContrast) return fgHex;
|
|
506
|
+
const [h, s, l] = $d016ad53434219a1$var$hexToHsl(fgHex);
|
|
507
|
+
if (bgLuminance > 0.5) {
|
|
508
|
+
// Light background — darken and boost saturation
|
|
509
|
+
const targetL = Math.max(0.08, l - (minContrast - diff) * 1.5);
|
|
510
|
+
const targetS = Math.min(1, s + 0.2);
|
|
511
|
+
return $d016ad53434219a1$var$hslToHex(h, targetS, targetL);
|
|
512
|
+
} else {
|
|
513
|
+
// Dark background — lighten and boost saturation
|
|
514
|
+
const targetL = Math.min(0.92, l + (minContrast - diff) * 1.5);
|
|
515
|
+
const targetS = Math.min(1, s + 0.15);
|
|
516
|
+
return $d016ad53434219a1$var$hslToHex(h, targetS, targetL);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function $d016ad53434219a1$export$4a3734b8c4b5c0e(hex, gradeHue, intensity) {
|
|
520
|
+
const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
|
|
521
|
+
// Blend hue toward the grade hue
|
|
522
|
+
const hueDiff = (gradeHue - h + 540) % 360 - 180;
|
|
523
|
+
const newH = (h + hueDiff * intensity * 0.3 + 360) % 360;
|
|
524
|
+
// Slightly unify saturation
|
|
525
|
+
const newS = Math.max(0, Math.min(1, s + (0.5 - s) * intensity * 0.15));
|
|
526
|
+
return $d016ad53434219a1$var$hslToHex(newH, newS, l);
|
|
527
|
+
}
|
|
528
|
+
function $d016ad53434219a1$export$6d1620b367f86f7a(rng) {
|
|
529
|
+
// Warm golden, cool blue, rosy, teal, amber
|
|
530
|
+
const GRADE_HUES = [
|
|
531
|
+
40,
|
|
532
|
+
220,
|
|
533
|
+
340,
|
|
534
|
+
175,
|
|
535
|
+
30
|
|
536
|
+
];
|
|
537
|
+
const hue = GRADE_HUES[Math.floor(rng() * GRADE_HUES.length)] + (rng() - 0.5) * 20;
|
|
538
|
+
const intensity = 0.15 + rng() * 0.25;
|
|
539
|
+
return {
|
|
540
|
+
hue: (hue + 360) % 360,
|
|
541
|
+
intensity: intensity
|
|
542
|
+
};
|
|
543
|
+
}
|
|
429
544
|
|
|
430
545
|
|
|
431
546
|
|
|
@@ -1206,10 +1321,172 @@ const $dd5df256f00f6199$export$c2fc138f94dd4b2a = {
|
|
|
1206
1321
|
};
|
|
1207
1322
|
|
|
1208
1323
|
|
|
1324
|
+
/**
|
|
1325
|
+
* Procedural shape generators — hash-derived shapes that are unique
|
|
1326
|
+
* per generation. Unlike the fixed shape library, these produce geometry
|
|
1327
|
+
* that doesn't repeat across hashes.
|
|
1328
|
+
*
|
|
1329
|
+
* All draw functions accept an RNG via the config parameter so the
|
|
1330
|
+
* shapes are deterministic from the hash.
|
|
1331
|
+
*/ const $6222456bc073291c$export$580f80cfb9de73bc = (ctx, size, config)=>{
|
|
1332
|
+
const rng = config?.rng ?? Math.random;
|
|
1333
|
+
const r = size / 2;
|
|
1334
|
+
const numPoints = 5 + Math.floor(rng() * 5); // 5-9 lobes
|
|
1335
|
+
const points = [];
|
|
1336
|
+
for(let i = 0; i < numPoints; i++){
|
|
1337
|
+
const angle = i / numPoints * Math.PI * 2;
|
|
1338
|
+
const jitter = 0.5 + rng() * 0.5; // radius varies 50-100%
|
|
1339
|
+
points.push({
|
|
1340
|
+
x: Math.cos(angle) * r * jitter,
|
|
1341
|
+
y: Math.sin(angle) * r * jitter
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
ctx.beginPath();
|
|
1345
|
+
// Start at midpoint between last and first point
|
|
1346
|
+
const last = points[points.length - 1];
|
|
1347
|
+
const first = points[0];
|
|
1348
|
+
ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
|
|
1349
|
+
for(let i = 0; i < numPoints; i++){
|
|
1350
|
+
const curr = points[i];
|
|
1351
|
+
const next = points[(i + 1) % numPoints];
|
|
1352
|
+
const midX = (curr.x + next.x) / 2;
|
|
1353
|
+
const midY = (curr.y + next.y) / 2;
|
|
1354
|
+
ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
|
|
1355
|
+
}
|
|
1356
|
+
ctx.closePath();
|
|
1357
|
+
};
|
|
1358
|
+
const $6222456bc073291c$export$7a6094023f0902a6 = (ctx, size, config)=>{
|
|
1359
|
+
const rng = config?.rng ?? Math.random;
|
|
1360
|
+
const r = size / 2;
|
|
1361
|
+
const sides = 3 + Math.floor(rng() * 10); // 3-12 sides
|
|
1362
|
+
const jitterAmount = 0.1 + rng() * 0.4; // 10-50% vertex displacement
|
|
1363
|
+
ctx.beginPath();
|
|
1364
|
+
for(let i = 0; i < sides; i++){
|
|
1365
|
+
const angle = i / sides * Math.PI * 2 - Math.PI / 2;
|
|
1366
|
+
const radiusJitter = 1 - jitterAmount + rng() * jitterAmount * 2;
|
|
1367
|
+
const x = Math.cos(angle) * r * radiusJitter;
|
|
1368
|
+
const y = Math.sin(angle) * r * radiusJitter;
|
|
1369
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1370
|
+
else ctx.lineTo(x, y);
|
|
1371
|
+
}
|
|
1372
|
+
ctx.closePath();
|
|
1373
|
+
};
|
|
1374
|
+
const $6222456bc073291c$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
|
|
1375
|
+
const rng = config?.rng ?? Math.random;
|
|
1376
|
+
const r = size / 2;
|
|
1377
|
+
// Frequency ratios — small integers produce recognizable patterns
|
|
1378
|
+
const freqA = 1 + Math.floor(rng() * 5); // 1-5
|
|
1379
|
+
const freqB = 1 + Math.floor(rng() * 5); // 1-5
|
|
1380
|
+
const phase = rng() * Math.PI; // phase offset
|
|
1381
|
+
const steps = 120;
|
|
1382
|
+
ctx.beginPath();
|
|
1383
|
+
for(let i = 0; i <= steps; i++){
|
|
1384
|
+
const t = i / steps * Math.PI * 2;
|
|
1385
|
+
const x = Math.sin(freqA * t + phase) * r;
|
|
1386
|
+
const y = Math.sin(freqB * t) * r;
|
|
1387
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1388
|
+
else ctx.lineTo(x, y);
|
|
1389
|
+
}
|
|
1390
|
+
ctx.closePath();
|
|
1391
|
+
};
|
|
1392
|
+
const $6222456bc073291c$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
1393
|
+
const rng = config?.rng ?? Math.random;
|
|
1394
|
+
const r = size / 2;
|
|
1395
|
+
// Exponent range: 0.3 (spiky astroid) to 5 (rounded rectangle)
|
|
1396
|
+
const n = 0.3 + rng() * 4.7;
|
|
1397
|
+
const steps = 120;
|
|
1398
|
+
ctx.beginPath();
|
|
1399
|
+
for(let i = 0; i <= steps; i++){
|
|
1400
|
+
const t = i / steps * Math.PI * 2;
|
|
1401
|
+
const cosT = Math.cos(t);
|
|
1402
|
+
const sinT = Math.sin(t);
|
|
1403
|
+
// Superellipse parametric form
|
|
1404
|
+
const x = Math.sign(cosT) * Math.pow(Math.abs(cosT), 2 / n) * r;
|
|
1405
|
+
const y = Math.sign(sinT) * Math.pow(Math.abs(sinT), 2 / n) * r;
|
|
1406
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1407
|
+
else ctx.lineTo(x, y);
|
|
1408
|
+
}
|
|
1409
|
+
ctx.closePath();
|
|
1410
|
+
};
|
|
1411
|
+
const $6222456bc073291c$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
1412
|
+
const rng = config?.rng ?? Math.random;
|
|
1413
|
+
const scale = size / 2;
|
|
1414
|
+
// R = outer radius, r = inner radius, d = pen distance from inner center
|
|
1415
|
+
const R = 1;
|
|
1416
|
+
const r = 0.2 + rng() * 0.6; // 0.2-0.8
|
|
1417
|
+
const d = 0.3 + rng() * 0.7; // 0.3-1.0
|
|
1418
|
+
// Number of full rotations needed to close the curve
|
|
1419
|
+
const gcd = (a, b)=>{
|
|
1420
|
+
const ai = Math.round(a * 1000);
|
|
1421
|
+
const bi = Math.round(b * 1000);
|
|
1422
|
+
const g = (x, y)=>y === 0 ? x : g(y, x % y);
|
|
1423
|
+
return g(ai, bi) / 1000;
|
|
1424
|
+
};
|
|
1425
|
+
const period = r / gcd(R, r);
|
|
1426
|
+
const maxT = Math.min(period, 10) * Math.PI * 2; // cap at 10 rotations
|
|
1427
|
+
const steps = Math.min(600, Math.floor(maxT * 20));
|
|
1428
|
+
ctx.beginPath();
|
|
1429
|
+
for(let i = 0; i <= steps; i++){
|
|
1430
|
+
const t = i / steps * maxT;
|
|
1431
|
+
const x = ((R - r) * Math.cos(t) + d * Math.cos((R - r) / r * t)) * scale / (1 + d);
|
|
1432
|
+
const y = ((R - r) * Math.sin(t) - d * Math.sin((R - r) / r * t)) * scale / (1 + d);
|
|
1433
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1434
|
+
else ctx.lineTo(x, y);
|
|
1435
|
+
}
|
|
1436
|
+
ctx.closePath();
|
|
1437
|
+
};
|
|
1438
|
+
const $6222456bc073291c$export$7608ccd03bfb705d = (ctx, size, config)=>{
|
|
1439
|
+
const rng = config?.rng ?? Math.random;
|
|
1440
|
+
const r = size / 2;
|
|
1441
|
+
const rings = 2 + Math.floor(rng() * 4); // 2-5 rings
|
|
1442
|
+
const freq = 3 + Math.floor(rng() * 12); // 3-14 waves per ring
|
|
1443
|
+
const amp = 0.05 + rng() * 0.15; // 5-20% of radius
|
|
1444
|
+
ctx.beginPath();
|
|
1445
|
+
for(let ring = 0; ring < rings; ring++){
|
|
1446
|
+
const baseR = r * (0.3 + ring / rings * 0.7);
|
|
1447
|
+
const steps = 80;
|
|
1448
|
+
for(let i = 0; i <= steps; i++){
|
|
1449
|
+
const t = i / steps * Math.PI * 2;
|
|
1450
|
+
const wave = Math.sin(t * freq + ring * 1.5) * baseR * amp;
|
|
1451
|
+
const x = Math.cos(t) * (baseR + wave);
|
|
1452
|
+
const y = Math.sin(t) * (baseR + wave);
|
|
1453
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1454
|
+
else ctx.lineTo(x, y);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
const $6222456bc073291c$export$11a377e7498bb523 = (ctx, size, config)=>{
|
|
1459
|
+
const rng = config?.rng ?? Math.random;
|
|
1460
|
+
const r = size / 2;
|
|
1461
|
+
const k = 2 + Math.floor(rng() * 6); // 2-7 petal parameter
|
|
1462
|
+
const steps = 200;
|
|
1463
|
+
ctx.beginPath();
|
|
1464
|
+
for(let i = 0; i <= steps; i++){
|
|
1465
|
+
const theta = i / steps * Math.PI * 2 * (k % 2 === 0 ? 1 : 2);
|
|
1466
|
+
const rr = Math.cos(k * theta) * r;
|
|
1467
|
+
const x = rr * Math.cos(theta);
|
|
1468
|
+
const y = rr * Math.sin(theta);
|
|
1469
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1470
|
+
else ctx.lineTo(x, y);
|
|
1471
|
+
}
|
|
1472
|
+
ctx.closePath();
|
|
1473
|
+
};
|
|
1474
|
+
const $6222456bc073291c$export$40cfb4c637f2fbb5 = {
|
|
1475
|
+
blob: $6222456bc073291c$export$580f80cfb9de73bc,
|
|
1476
|
+
ngon: $6222456bc073291c$export$7a6094023f0902a6,
|
|
1477
|
+
lissajous: $6222456bc073291c$export$ef56b4a8316e47d5,
|
|
1478
|
+
superellipse: $6222456bc073291c$export$1db9219b4f34658c,
|
|
1479
|
+
spirograph: $6222456bc073291c$export$b027c64d22b01985,
|
|
1480
|
+
waveRing: $6222456bc073291c$export$7608ccd03bfb705d,
|
|
1481
|
+
rose: $6222456bc073291c$export$11a377e7498bb523
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
|
|
1209
1485
|
const $9c828bde2acaae64$export$4ff7fc6f1af248b5 = {
|
|
1210
1486
|
...(0, $42f4f729ab090d17$export$492753207a5258e1),
|
|
1211
1487
|
...(0, $4bf3d69be49ad55c$export$dbe318a13ce51887),
|
|
1212
|
-
...(0, $dd5df256f00f6199$export$c2fc138f94dd4b2a)
|
|
1488
|
+
...(0, $dd5df256f00f6199$export$c2fc138f94dd4b2a),
|
|
1489
|
+
...(0, $6222456bc073291c$export$40cfb4c637f2fbb5)
|
|
1213
1490
|
};
|
|
1214
1491
|
|
|
1215
1492
|
|
|
@@ -1294,23 +1571,50 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1294
1571
|
break;
|
|
1295
1572
|
case "watercolor":
|
|
1296
1573
|
{
|
|
1297
|
-
//
|
|
1298
|
-
const passes =
|
|
1574
|
+
// Improved watercolor: edge darkening + radial bleed + layered washes
|
|
1575
|
+
const passes = 4 + (rng ? Math.floor(rng() * 2) : 0);
|
|
1299
1576
|
const savedAlpha = ctx.globalAlpha;
|
|
1300
|
-
|
|
1577
|
+
// Pass 1: Base wash — large, soft fill at low opacity
|
|
1578
|
+
ctx.globalAlpha = savedAlpha * 0.15;
|
|
1579
|
+
ctx.save();
|
|
1580
|
+
const baseScale = 1.08 + (rng ? rng() * 0.04 : 0);
|
|
1581
|
+
ctx.scale(baseScale, baseScale);
|
|
1582
|
+
ctx.fill();
|
|
1583
|
+
ctx.restore();
|
|
1584
|
+
// Pass 2: Multiple offset washes with radial displacement
|
|
1585
|
+
ctx.globalAlpha = savedAlpha * (0.25 / passes * 2);
|
|
1301
1586
|
for(let p = 0; p < passes; p++){
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1587
|
+
// Radial outward displacement (not uniform) for organic bleed
|
|
1588
|
+
const angle = rng ? rng() * Math.PI * 2 : p * Math.PI / 2;
|
|
1589
|
+
const dist = rng ? rng() * size * 0.05 : size * 0.02;
|
|
1590
|
+
const jx = Math.cos(angle) * dist;
|
|
1591
|
+
const jy = Math.sin(angle) * dist;
|
|
1304
1592
|
ctx.save();
|
|
1305
1593
|
ctx.translate(jx, jy);
|
|
1306
1594
|
ctx.fill();
|
|
1307
1595
|
ctx.restore();
|
|
1308
1596
|
}
|
|
1597
|
+
// Pass 3: Edge darkening — draw a slightly smaller shape with lighter fill
|
|
1598
|
+
// to simulate pigment pooling at boundaries
|
|
1599
|
+
ctx.globalAlpha = savedAlpha * 0.35;
|
|
1600
|
+
ctx.save();
|
|
1601
|
+
const innerScale = 0.85 + (rng ? rng() * 0.08 : 0);
|
|
1602
|
+
ctx.scale(innerScale, innerScale);
|
|
1603
|
+
// Lighten the fill for the inner area
|
|
1604
|
+
const origFill = ctx.fillStyle;
|
|
1605
|
+
if (typeof fillColor === "string") ctx.fillStyle = fillColor.replace(/[\d.]+\)$/, (m)=>{
|
|
1606
|
+
const v = parseFloat(m);
|
|
1607
|
+
return Math.min(1, v * 1.4).toFixed(2) + ")";
|
|
1608
|
+
});
|
|
1609
|
+
ctx.fill();
|
|
1610
|
+
ctx.fillStyle = origFill;
|
|
1611
|
+
ctx.restore();
|
|
1309
1612
|
ctx.globalAlpha = savedAlpha;
|
|
1310
|
-
//
|
|
1311
|
-
ctx.globalAlpha *= 0.
|
|
1613
|
+
// Soft stroke on top — thinner than normal for delicacy
|
|
1614
|
+
ctx.globalAlpha *= 0.25;
|
|
1615
|
+
ctx.lineWidth = strokeWidth * 0.6;
|
|
1312
1616
|
ctx.stroke();
|
|
1313
|
-
ctx.globalAlpha /= 0.
|
|
1617
|
+
ctx.globalAlpha /= 0.25;
|
|
1314
1618
|
break;
|
|
1315
1619
|
}
|
|
1316
1620
|
case "hatched":
|
|
@@ -1413,7 +1717,9 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1413
1717
|
ctx.lineWidth = strokeWidth;
|
|
1414
1718
|
const drawFunction = (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[shape];
|
|
1415
1719
|
if (drawFunction) {
|
|
1416
|
-
drawFunction(ctx, size
|
|
1720
|
+
drawFunction(ctx, size, {
|
|
1721
|
+
rng: rng
|
|
1722
|
+
});
|
|
1417
1723
|
$c3de8257a8baa3b0$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
1418
1724
|
}
|
|
1419
1725
|
// Reset shadow so patterns aren't double-glowed
|
|
@@ -1430,6 +1736,675 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1430
1736
|
|
|
1431
1737
|
|
|
1432
1738
|
|
|
1739
|
+
/**
|
|
1740
|
+
* Shape affinity system — controls which shapes look good together,
|
|
1741
|
+
* quality tiers for different rendering contexts, and size preferences.
|
|
1742
|
+
*
|
|
1743
|
+
* This replaces the naive "pick any shape" approach with intentional
|
|
1744
|
+
* curation that produces more cohesive compositions.
|
|
1745
|
+
*/ // ── Quality tiers ───────────────────────────────────────────────────
|
|
1746
|
+
// Not all shapes render equally well at all sizes or in all contexts.
|
|
1747
|
+
// Tier 1 shapes are visually strong at any size; Tier 3 shapes need
|
|
1748
|
+
// specific conditions to look good.
|
|
1749
|
+
const $e73976f898150d4d$export$4343b39fe47bd82c = {
|
|
1750
|
+
// ── Basic shapes ──────────────────────────────────────────────
|
|
1751
|
+
circle: {
|
|
1752
|
+
tier: 1,
|
|
1753
|
+
minSizeFraction: 0.05,
|
|
1754
|
+
maxSizeFraction: 1.0,
|
|
1755
|
+
affinities: [
|
|
1756
|
+
"circle",
|
|
1757
|
+
"blob",
|
|
1758
|
+
"hexagon",
|
|
1759
|
+
"flowerOfLife",
|
|
1760
|
+
"seedOfLife"
|
|
1761
|
+
],
|
|
1762
|
+
category: "basic",
|
|
1763
|
+
heroCandidate: false,
|
|
1764
|
+
bestStyles: [
|
|
1765
|
+
"fill-only",
|
|
1766
|
+
"watercolor",
|
|
1767
|
+
"fill-and-stroke"
|
|
1768
|
+
]
|
|
1769
|
+
},
|
|
1770
|
+
square: {
|
|
1771
|
+
tier: 2,
|
|
1772
|
+
minSizeFraction: 0.08,
|
|
1773
|
+
maxSizeFraction: 0.7,
|
|
1774
|
+
affinities: [
|
|
1775
|
+
"square",
|
|
1776
|
+
"diamond",
|
|
1777
|
+
"superellipse",
|
|
1778
|
+
"islamicPattern"
|
|
1779
|
+
],
|
|
1780
|
+
category: "basic",
|
|
1781
|
+
heroCandidate: false,
|
|
1782
|
+
bestStyles: [
|
|
1783
|
+
"fill-and-stroke",
|
|
1784
|
+
"stroke-only",
|
|
1785
|
+
"hatched"
|
|
1786
|
+
]
|
|
1787
|
+
},
|
|
1788
|
+
triangle: {
|
|
1789
|
+
tier: 1,
|
|
1790
|
+
minSizeFraction: 0.06,
|
|
1791
|
+
maxSizeFraction: 0.9,
|
|
1792
|
+
affinities: [
|
|
1793
|
+
"triangle",
|
|
1794
|
+
"diamond",
|
|
1795
|
+
"hexagon",
|
|
1796
|
+
"merkaba",
|
|
1797
|
+
"sriYantra"
|
|
1798
|
+
],
|
|
1799
|
+
category: "basic",
|
|
1800
|
+
heroCandidate: false,
|
|
1801
|
+
bestStyles: [
|
|
1802
|
+
"fill-and-stroke",
|
|
1803
|
+
"fill-only",
|
|
1804
|
+
"watercolor"
|
|
1805
|
+
]
|
|
1806
|
+
},
|
|
1807
|
+
hexagon: {
|
|
1808
|
+
tier: 1,
|
|
1809
|
+
minSizeFraction: 0.05,
|
|
1810
|
+
maxSizeFraction: 1.0,
|
|
1811
|
+
affinities: [
|
|
1812
|
+
"hexagon",
|
|
1813
|
+
"circle",
|
|
1814
|
+
"flowerOfLife",
|
|
1815
|
+
"metatronsCube",
|
|
1816
|
+
"triangle"
|
|
1817
|
+
],
|
|
1818
|
+
category: "basic",
|
|
1819
|
+
heroCandidate: false,
|
|
1820
|
+
bestStyles: [
|
|
1821
|
+
"fill-only",
|
|
1822
|
+
"fill-and-stroke",
|
|
1823
|
+
"watercolor"
|
|
1824
|
+
]
|
|
1825
|
+
},
|
|
1826
|
+
star: {
|
|
1827
|
+
tier: 2,
|
|
1828
|
+
minSizeFraction: 0.08,
|
|
1829
|
+
maxSizeFraction: 0.6,
|
|
1830
|
+
affinities: [
|
|
1831
|
+
"star",
|
|
1832
|
+
"circle",
|
|
1833
|
+
"mandala",
|
|
1834
|
+
"spirograph"
|
|
1835
|
+
],
|
|
1836
|
+
category: "basic",
|
|
1837
|
+
heroCandidate: false,
|
|
1838
|
+
bestStyles: [
|
|
1839
|
+
"fill-and-stroke",
|
|
1840
|
+
"stroke-only",
|
|
1841
|
+
"dashed"
|
|
1842
|
+
]
|
|
1843
|
+
},
|
|
1844
|
+
"jacked-star": {
|
|
1845
|
+
tier: 3,
|
|
1846
|
+
minSizeFraction: 0.1,
|
|
1847
|
+
maxSizeFraction: 0.4,
|
|
1848
|
+
affinities: [
|
|
1849
|
+
"star",
|
|
1850
|
+
"circle"
|
|
1851
|
+
],
|
|
1852
|
+
category: "basic",
|
|
1853
|
+
heroCandidate: false,
|
|
1854
|
+
bestStyles: [
|
|
1855
|
+
"stroke-only",
|
|
1856
|
+
"dashed"
|
|
1857
|
+
]
|
|
1858
|
+
},
|
|
1859
|
+
heart: {
|
|
1860
|
+
tier: 3,
|
|
1861
|
+
minSizeFraction: 0.1,
|
|
1862
|
+
maxSizeFraction: 0.5,
|
|
1863
|
+
affinities: [
|
|
1864
|
+
"circle",
|
|
1865
|
+
"blob"
|
|
1866
|
+
],
|
|
1867
|
+
category: "basic",
|
|
1868
|
+
heroCandidate: false,
|
|
1869
|
+
bestStyles: [
|
|
1870
|
+
"fill-only",
|
|
1871
|
+
"watercolor"
|
|
1872
|
+
]
|
|
1873
|
+
},
|
|
1874
|
+
diamond: {
|
|
1875
|
+
tier: 2,
|
|
1876
|
+
minSizeFraction: 0.06,
|
|
1877
|
+
maxSizeFraction: 0.8,
|
|
1878
|
+
affinities: [
|
|
1879
|
+
"diamond",
|
|
1880
|
+
"triangle",
|
|
1881
|
+
"square",
|
|
1882
|
+
"merkaba"
|
|
1883
|
+
],
|
|
1884
|
+
category: "basic",
|
|
1885
|
+
heroCandidate: false,
|
|
1886
|
+
bestStyles: [
|
|
1887
|
+
"fill-and-stroke",
|
|
1888
|
+
"fill-only",
|
|
1889
|
+
"double-stroke"
|
|
1890
|
+
]
|
|
1891
|
+
},
|
|
1892
|
+
cube: {
|
|
1893
|
+
tier: 3,
|
|
1894
|
+
minSizeFraction: 0.08,
|
|
1895
|
+
maxSizeFraction: 0.5,
|
|
1896
|
+
affinities: [
|
|
1897
|
+
"square",
|
|
1898
|
+
"diamond"
|
|
1899
|
+
],
|
|
1900
|
+
category: "basic",
|
|
1901
|
+
heroCandidate: false,
|
|
1902
|
+
bestStyles: [
|
|
1903
|
+
"stroke-only",
|
|
1904
|
+
"fill-and-stroke"
|
|
1905
|
+
]
|
|
1906
|
+
},
|
|
1907
|
+
// ── Complex shapes ────────────────────────────────────────────
|
|
1908
|
+
platonicSolid: {
|
|
1909
|
+
tier: 2,
|
|
1910
|
+
minSizeFraction: 0.15,
|
|
1911
|
+
maxSizeFraction: 0.8,
|
|
1912
|
+
affinities: [
|
|
1913
|
+
"metatronsCube",
|
|
1914
|
+
"merkaba",
|
|
1915
|
+
"hexagon",
|
|
1916
|
+
"triangle"
|
|
1917
|
+
],
|
|
1918
|
+
category: "complex",
|
|
1919
|
+
heroCandidate: true,
|
|
1920
|
+
bestStyles: [
|
|
1921
|
+
"stroke-only",
|
|
1922
|
+
"double-stroke",
|
|
1923
|
+
"dashed"
|
|
1924
|
+
]
|
|
1925
|
+
},
|
|
1926
|
+
fibonacciSpiral: {
|
|
1927
|
+
tier: 1,
|
|
1928
|
+
minSizeFraction: 0.2,
|
|
1929
|
+
maxSizeFraction: 1.0,
|
|
1930
|
+
affinities: [
|
|
1931
|
+
"circle",
|
|
1932
|
+
"rose",
|
|
1933
|
+
"spirograph",
|
|
1934
|
+
"flowerOfLife"
|
|
1935
|
+
],
|
|
1936
|
+
category: "complex",
|
|
1937
|
+
heroCandidate: true,
|
|
1938
|
+
bestStyles: [
|
|
1939
|
+
"stroke-only",
|
|
1940
|
+
"incomplete",
|
|
1941
|
+
"watercolor"
|
|
1942
|
+
]
|
|
1943
|
+
},
|
|
1944
|
+
islamicPattern: {
|
|
1945
|
+
tier: 2,
|
|
1946
|
+
minSizeFraction: 0.25,
|
|
1947
|
+
maxSizeFraction: 0.9,
|
|
1948
|
+
affinities: [
|
|
1949
|
+
"square",
|
|
1950
|
+
"hexagon",
|
|
1951
|
+
"star",
|
|
1952
|
+
"mandala"
|
|
1953
|
+
],
|
|
1954
|
+
category: "complex",
|
|
1955
|
+
heroCandidate: true,
|
|
1956
|
+
bestStyles: [
|
|
1957
|
+
"stroke-only",
|
|
1958
|
+
"dashed",
|
|
1959
|
+
"hatched"
|
|
1960
|
+
]
|
|
1961
|
+
},
|
|
1962
|
+
celticKnot: {
|
|
1963
|
+
tier: 2,
|
|
1964
|
+
minSizeFraction: 0.2,
|
|
1965
|
+
maxSizeFraction: 0.7,
|
|
1966
|
+
affinities: [
|
|
1967
|
+
"circle",
|
|
1968
|
+
"lissajous",
|
|
1969
|
+
"spirograph"
|
|
1970
|
+
],
|
|
1971
|
+
category: "complex",
|
|
1972
|
+
heroCandidate: true,
|
|
1973
|
+
bestStyles: [
|
|
1974
|
+
"stroke-only",
|
|
1975
|
+
"double-stroke"
|
|
1976
|
+
]
|
|
1977
|
+
},
|
|
1978
|
+
merkaba: {
|
|
1979
|
+
tier: 1,
|
|
1980
|
+
minSizeFraction: 0.15,
|
|
1981
|
+
maxSizeFraction: 1.0,
|
|
1982
|
+
affinities: [
|
|
1983
|
+
"triangle",
|
|
1984
|
+
"diamond",
|
|
1985
|
+
"sriYantra",
|
|
1986
|
+
"metatronsCube"
|
|
1987
|
+
],
|
|
1988
|
+
category: "complex",
|
|
1989
|
+
heroCandidate: true,
|
|
1990
|
+
bestStyles: [
|
|
1991
|
+
"stroke-only",
|
|
1992
|
+
"fill-and-stroke",
|
|
1993
|
+
"double-stroke"
|
|
1994
|
+
]
|
|
1995
|
+
},
|
|
1996
|
+
mandala: {
|
|
1997
|
+
tier: 1,
|
|
1998
|
+
minSizeFraction: 0.2,
|
|
1999
|
+
maxSizeFraction: 1.0,
|
|
2000
|
+
affinities: [
|
|
2001
|
+
"circle",
|
|
2002
|
+
"flowerOfLife",
|
|
2003
|
+
"spirograph",
|
|
2004
|
+
"rose"
|
|
2005
|
+
],
|
|
2006
|
+
category: "complex",
|
|
2007
|
+
heroCandidate: true,
|
|
2008
|
+
bestStyles: [
|
|
2009
|
+
"stroke-only",
|
|
2010
|
+
"dashed",
|
|
2011
|
+
"incomplete"
|
|
2012
|
+
]
|
|
2013
|
+
},
|
|
2014
|
+
fractal: {
|
|
2015
|
+
tier: 2,
|
|
2016
|
+
minSizeFraction: 0.2,
|
|
2017
|
+
maxSizeFraction: 0.8,
|
|
2018
|
+
affinities: [
|
|
2019
|
+
"blob",
|
|
2020
|
+
"lissajous",
|
|
2021
|
+
"circle"
|
|
2022
|
+
],
|
|
2023
|
+
category: "complex",
|
|
2024
|
+
heroCandidate: true,
|
|
2025
|
+
bestStyles: [
|
|
2026
|
+
"stroke-only",
|
|
2027
|
+
"incomplete"
|
|
2028
|
+
]
|
|
2029
|
+
},
|
|
2030
|
+
// ── Sacred shapes ─────────────────────────────────────────────
|
|
2031
|
+
flowerOfLife: {
|
|
2032
|
+
tier: 1,
|
|
2033
|
+
minSizeFraction: 0.2,
|
|
2034
|
+
maxSizeFraction: 1.0,
|
|
2035
|
+
affinities: [
|
|
2036
|
+
"circle",
|
|
2037
|
+
"hexagon",
|
|
2038
|
+
"seedOfLife",
|
|
2039
|
+
"eggOfLife",
|
|
2040
|
+
"metatronsCube"
|
|
2041
|
+
],
|
|
2042
|
+
category: "sacred",
|
|
2043
|
+
heroCandidate: true,
|
|
2044
|
+
bestStyles: [
|
|
2045
|
+
"stroke-only",
|
|
2046
|
+
"watercolor",
|
|
2047
|
+
"incomplete"
|
|
2048
|
+
]
|
|
2049
|
+
},
|
|
2050
|
+
treeOfLife: {
|
|
2051
|
+
tier: 2,
|
|
2052
|
+
minSizeFraction: 0.25,
|
|
2053
|
+
maxSizeFraction: 0.9,
|
|
2054
|
+
affinities: [
|
|
2055
|
+
"circle",
|
|
2056
|
+
"flowerOfLife",
|
|
2057
|
+
"metatronsCube"
|
|
2058
|
+
],
|
|
2059
|
+
category: "sacred",
|
|
2060
|
+
heroCandidate: true,
|
|
2061
|
+
bestStyles: [
|
|
2062
|
+
"stroke-only",
|
|
2063
|
+
"double-stroke"
|
|
2064
|
+
]
|
|
2065
|
+
},
|
|
2066
|
+
metatronsCube: {
|
|
2067
|
+
tier: 1,
|
|
2068
|
+
minSizeFraction: 0.2,
|
|
2069
|
+
maxSizeFraction: 1.0,
|
|
2070
|
+
affinities: [
|
|
2071
|
+
"hexagon",
|
|
2072
|
+
"flowerOfLife",
|
|
2073
|
+
"platonicSolid",
|
|
2074
|
+
"merkaba"
|
|
2075
|
+
],
|
|
2076
|
+
category: "sacred",
|
|
2077
|
+
heroCandidate: true,
|
|
2078
|
+
bestStyles: [
|
|
2079
|
+
"stroke-only",
|
|
2080
|
+
"dashed",
|
|
2081
|
+
"incomplete"
|
|
2082
|
+
]
|
|
2083
|
+
},
|
|
2084
|
+
sriYantra: {
|
|
2085
|
+
tier: 1,
|
|
2086
|
+
minSizeFraction: 0.2,
|
|
2087
|
+
maxSizeFraction: 1.0,
|
|
2088
|
+
affinities: [
|
|
2089
|
+
"triangle",
|
|
2090
|
+
"merkaba",
|
|
2091
|
+
"mandala",
|
|
2092
|
+
"diamond"
|
|
2093
|
+
],
|
|
2094
|
+
category: "sacred",
|
|
2095
|
+
heroCandidate: true,
|
|
2096
|
+
bestStyles: [
|
|
2097
|
+
"stroke-only",
|
|
2098
|
+
"fill-and-stroke",
|
|
2099
|
+
"double-stroke"
|
|
2100
|
+
]
|
|
2101
|
+
},
|
|
2102
|
+
seedOfLife: {
|
|
2103
|
+
tier: 1,
|
|
2104
|
+
minSizeFraction: 0.15,
|
|
2105
|
+
maxSizeFraction: 0.9,
|
|
2106
|
+
affinities: [
|
|
2107
|
+
"circle",
|
|
2108
|
+
"flowerOfLife",
|
|
2109
|
+
"eggOfLife",
|
|
2110
|
+
"hexagon"
|
|
2111
|
+
],
|
|
2112
|
+
category: "sacred",
|
|
2113
|
+
heroCandidate: true,
|
|
2114
|
+
bestStyles: [
|
|
2115
|
+
"stroke-only",
|
|
2116
|
+
"watercolor",
|
|
2117
|
+
"fill-only"
|
|
2118
|
+
]
|
|
2119
|
+
},
|
|
2120
|
+
vesicaPiscis: {
|
|
2121
|
+
tier: 2,
|
|
2122
|
+
minSizeFraction: 0.15,
|
|
2123
|
+
maxSizeFraction: 0.7,
|
|
2124
|
+
affinities: [
|
|
2125
|
+
"circle",
|
|
2126
|
+
"seedOfLife",
|
|
2127
|
+
"flowerOfLife"
|
|
2128
|
+
],
|
|
2129
|
+
category: "sacred",
|
|
2130
|
+
heroCandidate: false,
|
|
2131
|
+
bestStyles: [
|
|
2132
|
+
"stroke-only",
|
|
2133
|
+
"watercolor"
|
|
2134
|
+
]
|
|
2135
|
+
},
|
|
2136
|
+
torus: {
|
|
2137
|
+
tier: 3,
|
|
2138
|
+
minSizeFraction: 0.2,
|
|
2139
|
+
maxSizeFraction: 0.6,
|
|
2140
|
+
affinities: [
|
|
2141
|
+
"circle",
|
|
2142
|
+
"spirograph",
|
|
2143
|
+
"waveRing"
|
|
2144
|
+
],
|
|
2145
|
+
category: "sacred",
|
|
2146
|
+
heroCandidate: false,
|
|
2147
|
+
bestStyles: [
|
|
2148
|
+
"stroke-only",
|
|
2149
|
+
"dashed"
|
|
2150
|
+
]
|
|
2151
|
+
},
|
|
2152
|
+
eggOfLife: {
|
|
2153
|
+
tier: 2,
|
|
2154
|
+
minSizeFraction: 0.15,
|
|
2155
|
+
maxSizeFraction: 0.8,
|
|
2156
|
+
affinities: [
|
|
2157
|
+
"circle",
|
|
2158
|
+
"seedOfLife",
|
|
2159
|
+
"flowerOfLife"
|
|
2160
|
+
],
|
|
2161
|
+
category: "sacred",
|
|
2162
|
+
heroCandidate: true,
|
|
2163
|
+
bestStyles: [
|
|
2164
|
+
"stroke-only",
|
|
2165
|
+
"watercolor"
|
|
2166
|
+
]
|
|
2167
|
+
},
|
|
2168
|
+
// ── Procedural shapes ─────────────────────────────────────────
|
|
2169
|
+
blob: {
|
|
2170
|
+
tier: 1,
|
|
2171
|
+
minSizeFraction: 0.05,
|
|
2172
|
+
maxSizeFraction: 1.0,
|
|
2173
|
+
affinities: [
|
|
2174
|
+
"blob",
|
|
2175
|
+
"circle",
|
|
2176
|
+
"superellipse",
|
|
2177
|
+
"waveRing"
|
|
2178
|
+
],
|
|
2179
|
+
category: "procedural",
|
|
2180
|
+
heroCandidate: false,
|
|
2181
|
+
bestStyles: [
|
|
2182
|
+
"fill-only",
|
|
2183
|
+
"watercolor",
|
|
2184
|
+
"fill-and-stroke"
|
|
2185
|
+
]
|
|
2186
|
+
},
|
|
2187
|
+
ngon: {
|
|
2188
|
+
tier: 2,
|
|
2189
|
+
minSizeFraction: 0.06,
|
|
2190
|
+
maxSizeFraction: 0.8,
|
|
2191
|
+
affinities: [
|
|
2192
|
+
"hexagon",
|
|
2193
|
+
"triangle",
|
|
2194
|
+
"diamond",
|
|
2195
|
+
"superellipse"
|
|
2196
|
+
],
|
|
2197
|
+
category: "procedural",
|
|
2198
|
+
heroCandidate: false,
|
|
2199
|
+
bestStyles: [
|
|
2200
|
+
"fill-and-stroke",
|
|
2201
|
+
"fill-only",
|
|
2202
|
+
"hatched"
|
|
2203
|
+
]
|
|
2204
|
+
},
|
|
2205
|
+
lissajous: {
|
|
2206
|
+
tier: 2,
|
|
2207
|
+
minSizeFraction: 0.15,
|
|
2208
|
+
maxSizeFraction: 0.8,
|
|
2209
|
+
affinities: [
|
|
2210
|
+
"spirograph",
|
|
2211
|
+
"rose",
|
|
2212
|
+
"celticKnot",
|
|
2213
|
+
"fibonacciSpiral"
|
|
2214
|
+
],
|
|
2215
|
+
category: "procedural",
|
|
2216
|
+
heroCandidate: false,
|
|
2217
|
+
bestStyles: [
|
|
2218
|
+
"stroke-only",
|
|
2219
|
+
"incomplete",
|
|
2220
|
+
"dashed"
|
|
2221
|
+
]
|
|
2222
|
+
},
|
|
2223
|
+
superellipse: {
|
|
2224
|
+
tier: 1,
|
|
2225
|
+
minSizeFraction: 0.05,
|
|
2226
|
+
maxSizeFraction: 1.0,
|
|
2227
|
+
affinities: [
|
|
2228
|
+
"circle",
|
|
2229
|
+
"square",
|
|
2230
|
+
"blob",
|
|
2231
|
+
"hexagon"
|
|
2232
|
+
],
|
|
2233
|
+
category: "procedural",
|
|
2234
|
+
heroCandidate: false,
|
|
2235
|
+
bestStyles: [
|
|
2236
|
+
"fill-only",
|
|
2237
|
+
"watercolor",
|
|
2238
|
+
"fill-and-stroke"
|
|
2239
|
+
]
|
|
2240
|
+
},
|
|
2241
|
+
spirograph: {
|
|
2242
|
+
tier: 1,
|
|
2243
|
+
minSizeFraction: 0.15,
|
|
2244
|
+
maxSizeFraction: 0.9,
|
|
2245
|
+
affinities: [
|
|
2246
|
+
"rose",
|
|
2247
|
+
"lissajous",
|
|
2248
|
+
"mandala",
|
|
2249
|
+
"flowerOfLife"
|
|
2250
|
+
],
|
|
2251
|
+
category: "procedural",
|
|
2252
|
+
heroCandidate: true,
|
|
2253
|
+
bestStyles: [
|
|
2254
|
+
"stroke-only",
|
|
2255
|
+
"incomplete",
|
|
2256
|
+
"dashed"
|
|
2257
|
+
]
|
|
2258
|
+
},
|
|
2259
|
+
waveRing: {
|
|
2260
|
+
tier: 2,
|
|
2261
|
+
minSizeFraction: 0.1,
|
|
2262
|
+
maxSizeFraction: 0.8,
|
|
2263
|
+
affinities: [
|
|
2264
|
+
"circle",
|
|
2265
|
+
"blob",
|
|
2266
|
+
"torus",
|
|
2267
|
+
"spirograph"
|
|
2268
|
+
],
|
|
2269
|
+
category: "procedural",
|
|
2270
|
+
heroCandidate: false,
|
|
2271
|
+
bestStyles: [
|
|
2272
|
+
"stroke-only",
|
|
2273
|
+
"dashed",
|
|
2274
|
+
"incomplete"
|
|
2275
|
+
]
|
|
2276
|
+
},
|
|
2277
|
+
rose: {
|
|
2278
|
+
tier: 1,
|
|
2279
|
+
minSizeFraction: 0.1,
|
|
2280
|
+
maxSizeFraction: 0.9,
|
|
2281
|
+
affinities: [
|
|
2282
|
+
"spirograph",
|
|
2283
|
+
"mandala",
|
|
2284
|
+
"flowerOfLife",
|
|
2285
|
+
"circle"
|
|
2286
|
+
],
|
|
2287
|
+
category: "procedural",
|
|
2288
|
+
heroCandidate: true,
|
|
2289
|
+
bestStyles: [
|
|
2290
|
+
"stroke-only",
|
|
2291
|
+
"fill-only",
|
|
2292
|
+
"watercolor"
|
|
2293
|
+
]
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
function $e73976f898150d4d$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
|
|
2297
|
+
const available = shapeNames.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]);
|
|
2298
|
+
// Pick a seed shape — tier 1 shapes that are hero candidates
|
|
2299
|
+
const heroPool = available.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s].tier === 1 && $e73976f898150d4d$export$4343b39fe47bd82c[s].heroCandidate);
|
|
2300
|
+
const seedShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : available[Math.floor(rng() * available.length)];
|
|
2301
|
+
const seedProfile = $e73976f898150d4d$export$4343b39fe47bd82c[seedShape];
|
|
2302
|
+
// Primary: seed shape + its direct affinities (tier 1-2 only)
|
|
2303
|
+
const primaryCandidates = [
|
|
2304
|
+
seedShape,
|
|
2305
|
+
...seedProfile.affinities
|
|
2306
|
+
].filter((s)=>available.includes(s)).filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s].tier <= 2);
|
|
2307
|
+
const primary = [
|
|
2308
|
+
...new Set(primaryCandidates)
|
|
2309
|
+
].slice(0, 5);
|
|
2310
|
+
// Supporting: affinities of affinities, plus same-category shapes
|
|
2311
|
+
const supportingSet = new Set();
|
|
2312
|
+
for (const p of primary){
|
|
2313
|
+
const profile = $e73976f898150d4d$export$4343b39fe47bd82c[p];
|
|
2314
|
+
if (!profile) continue;
|
|
2315
|
+
for (const aff of profile.affinities)if (available.includes(aff) && !primary.includes(aff)) supportingSet.add(aff);
|
|
2316
|
+
}
|
|
2317
|
+
// Add same-category tier 1-2 shapes
|
|
2318
|
+
for (const s of available){
|
|
2319
|
+
const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
|
|
2320
|
+
if (p.category === seedProfile.category && p.tier <= 2 && !primary.includes(s)) supportingSet.add(s);
|
|
2321
|
+
}
|
|
2322
|
+
const supporting = [
|
|
2323
|
+
...supportingSet
|
|
2324
|
+
].slice(0, 6);
|
|
2325
|
+
// Accents: tier 1 shapes from other categories for contrast
|
|
2326
|
+
const usedCategories = new Set([
|
|
2327
|
+
...primary,
|
|
2328
|
+
...supporting
|
|
2329
|
+
].map((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]?.category));
|
|
2330
|
+
const accentCandidates = available.filter((s)=>!primary.includes(s) && !supporting.includes(s) && $e73976f898150d4d$export$4343b39fe47bd82c[s].tier <= 2 && !usedCategories.has($e73976f898150d4d$export$4343b39fe47bd82c[s].category));
|
|
2331
|
+
// Shuffle and take a few
|
|
2332
|
+
const accents = [];
|
|
2333
|
+
const shuffled = [
|
|
2334
|
+
...accentCandidates
|
|
2335
|
+
];
|
|
2336
|
+
for(let i = shuffled.length - 1; i > 0; i--){
|
|
2337
|
+
const j = Math.floor(rng() * (i + 1));
|
|
2338
|
+
[shuffled[i], shuffled[j]] = [
|
|
2339
|
+
shuffled[j],
|
|
2340
|
+
shuffled[i]
|
|
2341
|
+
];
|
|
2342
|
+
}
|
|
2343
|
+
accents.push(...shuffled.slice(0, 3));
|
|
2344
|
+
// For certain archetypes, bias the palette
|
|
2345
|
+
if (archetypeName === "geometric-precision") // Remove blobs and organic shapes from primary
|
|
2346
|
+
return {
|
|
2347
|
+
primary: primary.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]?.category !== "procedural" || s === "ngon"),
|
|
2348
|
+
supporting: supporting.filter((s)=>s !== "blob"),
|
|
2349
|
+
accents: accents
|
|
2350
|
+
};
|
|
2351
|
+
if (archetypeName === "organic-flow") {
|
|
2352
|
+
// Boost procedural/organic shapes
|
|
2353
|
+
const organicBoost = available.filter((s)=>[
|
|
2354
|
+
"blob",
|
|
2355
|
+
"superellipse",
|
|
2356
|
+
"waveRing",
|
|
2357
|
+
"rose"
|
|
2358
|
+
].includes(s) && !primary.includes(s));
|
|
2359
|
+
return {
|
|
2360
|
+
primary: [
|
|
2361
|
+
...primary,
|
|
2362
|
+
...organicBoost.slice(0, 2)
|
|
2363
|
+
],
|
|
2364
|
+
supporting: supporting,
|
|
2365
|
+
accents: accents
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
return {
|
|
2369
|
+
primary: primary,
|
|
2370
|
+
supporting: supporting,
|
|
2371
|
+
accents: accents
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
function $e73976f898150d4d$export$3c37d9a045754d0e(palette, rng, sizeFraction) {
|
|
2375
|
+
// Filter each tier by size constraints
|
|
2376
|
+
const validPrimary = palette.primary.filter((s)=>{
|
|
2377
|
+
const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
|
|
2378
|
+
return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
|
|
2379
|
+
});
|
|
2380
|
+
const validSupporting = palette.supporting.filter((s)=>{
|
|
2381
|
+
const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
|
|
2382
|
+
return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
|
|
2383
|
+
});
|
|
2384
|
+
const validAccents = palette.accents.filter((s)=>{
|
|
2385
|
+
const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
|
|
2386
|
+
return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
|
|
2387
|
+
});
|
|
2388
|
+
const roll = rng();
|
|
2389
|
+
if (roll < 0.60 && validPrimary.length > 0) return validPrimary[Math.floor(rng() * validPrimary.length)];
|
|
2390
|
+
if (roll < 0.90 && validSupporting.length > 0) return validSupporting[Math.floor(rng() * validSupporting.length)];
|
|
2391
|
+
if (validAccents.length > 0) return validAccents[Math.floor(rng() * validAccents.length)];
|
|
2392
|
+
// Fallback: any valid primary or supporting
|
|
2393
|
+
const fallback = [
|
|
2394
|
+
...validPrimary,
|
|
2395
|
+
...validSupporting
|
|
2396
|
+
];
|
|
2397
|
+
if (fallback.length > 0) return fallback[Math.floor(rng() * fallback.length)];
|
|
2398
|
+
// Ultimate fallback
|
|
2399
|
+
return palette.primary[0] || "circle";
|
|
2400
|
+
}
|
|
2401
|
+
function $e73976f898150d4d$export$ab873bb6fb56c1a8(shapeName, layerStyle, rng) {
|
|
2402
|
+
const profile = $e73976f898150d4d$export$4343b39fe47bd82c[shapeName];
|
|
2403
|
+
if (!profile || rng() > 0.7) return layerStyle;
|
|
2404
|
+
return profile.bestStyles[Math.floor(rng() * profile.bestStyles.length)];
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
|
|
1433
2408
|
|
|
1434
2409
|
/**
|
|
1435
2410
|
* Configuration options for image generation.
|
|
@@ -1644,6 +2619,69 @@ const $f89bc858f7202849$var$ARCHETYPES = [
|
|
|
1644
2619
|
sizePower: 2.5,
|
|
1645
2620
|
invertForeground: false
|
|
1646
2621
|
},
|
|
2622
|
+
{
|
|
2623
|
+
name: "watercolor-wash",
|
|
2624
|
+
gridSize: 3,
|
|
2625
|
+
layers: 3,
|
|
2626
|
+
baseOpacity: 0.25,
|
|
2627
|
+
opacityReduction: 0.03,
|
|
2628
|
+
minShapeSize: 200,
|
|
2629
|
+
maxShapeSize: 700,
|
|
2630
|
+
backgroundStyle: "radial-light",
|
|
2631
|
+
paletteMode: "harmonious",
|
|
2632
|
+
preferredStyles: [
|
|
2633
|
+
"watercolor",
|
|
2634
|
+
"fill-only",
|
|
2635
|
+
"incomplete"
|
|
2636
|
+
],
|
|
2637
|
+
flowLineMultiplier: 0.5,
|
|
2638
|
+
heroShape: false,
|
|
2639
|
+
glowMultiplier: 0.3,
|
|
2640
|
+
sizePower: 0.6,
|
|
2641
|
+
invertForeground: false
|
|
2642
|
+
},
|
|
2643
|
+
{
|
|
2644
|
+
name: "op-art",
|
|
2645
|
+
gridSize: 8,
|
|
2646
|
+
layers: 2,
|
|
2647
|
+
baseOpacity: 0.95,
|
|
2648
|
+
opacityReduction: 0.05,
|
|
2649
|
+
minShapeSize: 20,
|
|
2650
|
+
maxShapeSize: 200,
|
|
2651
|
+
backgroundStyle: "solid-light",
|
|
2652
|
+
paletteMode: "high-contrast",
|
|
2653
|
+
preferredStyles: [
|
|
2654
|
+
"fill-and-stroke",
|
|
2655
|
+
"stroke-only",
|
|
2656
|
+
"dashed"
|
|
2657
|
+
],
|
|
2658
|
+
flowLineMultiplier: 0,
|
|
2659
|
+
heroShape: false,
|
|
2660
|
+
glowMultiplier: 0,
|
|
2661
|
+
sizePower: 0.4,
|
|
2662
|
+
invertForeground: false
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
name: "collage",
|
|
2666
|
+
gridSize: 4,
|
|
2667
|
+
layers: 3,
|
|
2668
|
+
baseOpacity: 0.9,
|
|
2669
|
+
opacityReduction: 0.08,
|
|
2670
|
+
minShapeSize: 80,
|
|
2671
|
+
maxShapeSize: 500,
|
|
2672
|
+
backgroundStyle: "solid-light",
|
|
2673
|
+
paletteMode: "duotone",
|
|
2674
|
+
preferredStyles: [
|
|
2675
|
+
"fill-and-stroke",
|
|
2676
|
+
"fill-only",
|
|
2677
|
+
"double-stroke"
|
|
2678
|
+
],
|
|
2679
|
+
flowLineMultiplier: 0,
|
|
2680
|
+
heroShape: true,
|
|
2681
|
+
glowMultiplier: 0,
|
|
2682
|
+
sizePower: 0.7,
|
|
2683
|
+
invertForeground: false
|
|
2684
|
+
},
|
|
1647
2685
|
{
|
|
1648
2686
|
name: "classic",
|
|
1649
2687
|
gridSize: 5,
|
|
@@ -1671,26 +2709,7 @@ function $f89bc858f7202849$export$f1142fd7da4d6590(rng) {
|
|
|
1671
2709
|
}
|
|
1672
2710
|
|
|
1673
2711
|
|
|
1674
|
-
// ── Shape categories for weighted selection
|
|
1675
|
-
const $4f72c5a314eddf25$var$BASIC_SHAPES = [
|
|
1676
|
-
"circle",
|
|
1677
|
-
"square",
|
|
1678
|
-
"triangle",
|
|
1679
|
-
"hexagon",
|
|
1680
|
-
"diamond",
|
|
1681
|
-
"cube"
|
|
1682
|
-
];
|
|
1683
|
-
const $4f72c5a314eddf25$var$COMPLEX_SHAPES = [
|
|
1684
|
-
"star",
|
|
1685
|
-
"jacked-star",
|
|
1686
|
-
"heart",
|
|
1687
|
-
"platonicSolid",
|
|
1688
|
-
"fibonacciSpiral",
|
|
1689
|
-
"islamicPattern",
|
|
1690
|
-
"celticKnot",
|
|
1691
|
-
"merkaba",
|
|
1692
|
-
"fractal"
|
|
1693
|
-
];
|
|
2712
|
+
// ── Shape categories for weighted selection (legacy fallback) ───────
|
|
1694
2713
|
const $4f72c5a314eddf25$var$SACRED_SHAPES = [
|
|
1695
2714
|
"mandala",
|
|
1696
2715
|
"flowerOfLife",
|
|
@@ -1709,21 +2728,6 @@ const $4f72c5a314eddf25$var$COMPOSITION_MODES = [
|
|
|
1709
2728
|
"grid-subdivision",
|
|
1710
2729
|
"clustered"
|
|
1711
2730
|
];
|
|
1712
|
-
// ── Helper: pick shape with layer-aware weighting ───────────────────
|
|
1713
|
-
function $4f72c5a314eddf25$var$pickShape(rng, layerRatio, shapeNames) {
|
|
1714
|
-
const basicW = 1 - layerRatio * 0.6;
|
|
1715
|
-
const complexW = 0.3 + layerRatio * 0.3;
|
|
1716
|
-
const sacredW = 0.1 + layerRatio * 0.4;
|
|
1717
|
-
const total = basicW + complexW + sacredW;
|
|
1718
|
-
const roll = rng() * total;
|
|
1719
|
-
let pool;
|
|
1720
|
-
if (roll < basicW) pool = $4f72c5a314eddf25$var$BASIC_SHAPES;
|
|
1721
|
-
else if (roll < basicW + complexW) pool = $4f72c5a314eddf25$var$COMPLEX_SHAPES;
|
|
1722
|
-
else pool = $4f72c5a314eddf25$var$SACRED_SHAPES;
|
|
1723
|
-
const available = pool.filter((s)=>shapeNames.includes(s));
|
|
1724
|
-
if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
1725
|
-
return available[Math.floor(rng() * available.length)];
|
|
1726
|
-
}
|
|
1727
2731
|
// ── Helper: get position based on composition mode ──────────────────
|
|
1728
2732
|
function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
1729
2733
|
switch(mode){
|
|
@@ -1782,22 +2786,28 @@ function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1782
2786
|
};
|
|
1783
2787
|
}
|
|
1784
2788
|
}
|
|
1785
|
-
// ── Helper: positional color
|
|
1786
|
-
function $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height,
|
|
1787
|
-
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
return (0, $d016ad53434219a1$export$
|
|
2789
|
+
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
2790
|
+
function $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, hierarchy, rng) {
|
|
2791
|
+
// Blend position into color selection — shapes near center lean dominant
|
|
2792
|
+
const distFromCenter = Math.hypot(x - width / 2, y - height / 2) / Math.hypot(width / 2, height / 2);
|
|
2793
|
+
// Center = more dominant, edges = more accent
|
|
2794
|
+
if (distFromCenter < 0.35) return (0, $d016ad53434219a1$export$18a34c25ea7e724b)(hierarchy.dominant, rng, 10, 0.08);
|
|
2795
|
+
else if (distFromCenter < 0.7) return (0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(hierarchy, rng), rng, 8, 0.06);
|
|
2796
|
+
else {
|
|
2797
|
+
// Edges: bias toward secondary/accent
|
|
2798
|
+
const roll = rng();
|
|
2799
|
+
const color = roll < 0.4 ? hierarchy.secondary : roll < 0.75 ? hierarchy.accent : hierarchy.dominant;
|
|
2800
|
+
return (0, $d016ad53434219a1$export$18a34c25ea7e724b)(color, rng, 12, 0.08);
|
|
2801
|
+
}
|
|
1792
2802
|
}
|
|
1793
|
-
// ── Helper: check if a position is inside a void zone
|
|
2803
|
+
// ── Helper: check if a position is inside a void zone ───────────────
|
|
1794
2804
|
function $4f72c5a314eddf25$var$isInVoidZone(x, y, voidZones) {
|
|
1795
2805
|
for (const zone of voidZones){
|
|
1796
2806
|
if (Math.hypot(x - zone.x, y - zone.y) < zone.radius) return true;
|
|
1797
2807
|
}
|
|
1798
2808
|
return false;
|
|
1799
2809
|
}
|
|
1800
|
-
// ── Helper: density check
|
|
2810
|
+
// ── Helper: density check ───────────────────────────────────────────
|
|
1801
2811
|
function $4f72c5a314eddf25$var$localDensity(x, y, positions, radius) {
|
|
1802
2812
|
let count = 0;
|
|
1803
2813
|
for (const p of positions)if (Math.hypot(x - p.x, y - p.y) < radius) count++;
|
|
@@ -1893,7 +2903,15 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1893
2903
|
const [bgStart, bgEnd] = colorScheme.getBackgroundColorsByMode(archetype.paletteMode);
|
|
1894
2904
|
const tempMode = colorScheme.getTemperatureMode();
|
|
1895
2905
|
const fgTempTarget = tempMode === "warm-bg" ? "cool" : tempMode === "cool-bg" ? "warm" : null;
|
|
2906
|
+
// ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
|
|
2907
|
+
const colorHierarchy = (0, $d016ad53434219a1$export$fabac4600b87056)(colors, rng);
|
|
2908
|
+
// ── 0c. Shape palette — curated shapes that work well together ──
|
|
1896
2909
|
const shapeNames = Object.keys((0, $9c828bde2acaae64$export$4ff7fc6f1af248b5));
|
|
2910
|
+
const shapePalette = (0, $e73976f898150d4d$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
|
|
2911
|
+
// ── 0d. Color grading — unified tone for the whole image ───────
|
|
2912
|
+
const colorGrade = (0, $d016ad53434219a1$export$6d1620b367f86f7a)(rng);
|
|
2913
|
+
// ── 0e. Light direction — consistent shadow angle ──────────────
|
|
2914
|
+
const lightAngle = rng() * Math.PI * 2;
|
|
1897
2915
|
const scaleFactor = Math.min(width, height) / 1024;
|
|
1898
2916
|
const adjustedMinSize = minShapeSize * scaleFactor;
|
|
1899
2917
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
@@ -1902,25 +2920,45 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1902
2920
|
// ── 1. Background ──────────────────────────────────────────────
|
|
1903
2921
|
const bgRadius = Math.hypot(cx, cy);
|
|
1904
2922
|
$4f72c5a314eddf25$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
|
|
1905
|
-
//
|
|
1906
|
-
|
|
2923
|
+
// Gradient mesh overlay — 3-4 color control points for richer backgrounds
|
|
2924
|
+
const meshPoints = 3 + Math.floor(rng() * 2);
|
|
2925
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
2926
|
+
for(let i = 0; i < meshPoints; i++){
|
|
2927
|
+
const mx = rng() * width;
|
|
2928
|
+
const my = rng() * height;
|
|
2929
|
+
const mRadius = Math.min(width, height) * (0.3 + rng() * 0.4);
|
|
2930
|
+
const mColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
2931
|
+
const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
|
|
2932
|
+
grad.addColorStop(0, (0, $d016ad53434219a1$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
|
|
2933
|
+
grad.addColorStop(1, "rgba(0,0,0,0)");
|
|
2934
|
+
ctx.globalAlpha = 1;
|
|
2935
|
+
ctx.fillStyle = grad;
|
|
2936
|
+
ctx.fillRect(0, 0, width, height);
|
|
2937
|
+
}
|
|
2938
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2939
|
+
// Compute average background luminance for contrast enforcement
|
|
2940
|
+
const bgLum = ((0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
|
|
2941
|
+
// ── 1b. Layered background — archetype-coherent shapes ─────────
|
|
1907
2942
|
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
1908
2943
|
ctx.globalCompositeOperation = "soft-light";
|
|
1909
2944
|
for(let i = 0; i < bgShapeCount; i++){
|
|
1910
2945
|
const bx = rng() * width;
|
|
1911
2946
|
const by = rng() * height;
|
|
1912
2947
|
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
1913
|
-
const bColor =
|
|
2948
|
+
const bColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
1914
2949
|
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
1915
2950
|
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(bColor, 0.15);
|
|
1916
2951
|
ctx.beginPath();
|
|
1917
|
-
|
|
2952
|
+
// Use archetype-appropriate background shapes
|
|
2953
|
+
if (archetype.name === "geometric-precision" || archetype.name === "op-art") // Rectangular shapes for geometric archetypes
|
|
2954
|
+
ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
2955
|
+
else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
1918
2956
|
ctx.fill();
|
|
1919
2957
|
}
|
|
1920
2958
|
// Subtle concentric rings from center
|
|
1921
2959
|
const ringCount = 2 + Math.floor(rng() * 3);
|
|
1922
2960
|
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
1923
|
-
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(
|
|
2961
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
1924
2962
|
ctx.lineWidth = 1 * scaleFactor;
|
|
1925
2963
|
for(let i = 1; i <= ringCount; i++){
|
|
1926
2964
|
const r = Math.min(width, height) * 0.15 * i;
|
|
@@ -1934,7 +2972,6 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1934
2972
|
const symRoll = rng();
|
|
1935
2973
|
const symmetryMode = symRoll < 0.10 ? "bilateral-x" : symRoll < 0.20 ? "bilateral-y" : symRoll < 0.25 ? "quad" : "none";
|
|
1936
2974
|
// ── 3. Focal points + void zones ───────────────────────────────
|
|
1937
|
-
// Rule-of-thirds intersection points for intentional composition
|
|
1938
2975
|
const THIRDS_POINTS = [
|
|
1939
2976
|
{
|
|
1940
2977
|
x: 1 / 3,
|
|
@@ -1955,10 +2992,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1955
2992
|
];
|
|
1956
2993
|
const numFocal = 1 + Math.floor(rng() * 2);
|
|
1957
2994
|
const focalPoints = [];
|
|
1958
|
-
for(let f = 0; f < numFocal; f++)
|
|
1959
|
-
if (rng() < 0.7) {
|
|
2995
|
+
for(let f = 0; f < numFocal; f++)if (rng() < 0.7) {
|
|
1960
2996
|
const tp = THIRDS_POINTS[Math.floor(rng() * THIRDS_POINTS.length)];
|
|
1961
|
-
// Small jitter around the thirds point so it's not robotic
|
|
1962
2997
|
focalPoints.push({
|
|
1963
2998
|
x: width * (tp.x + (rng() - 0.5) * 0.08),
|
|
1964
2999
|
y: height * (tp.y + (rng() - 0.5) * 0.08),
|
|
@@ -1969,7 +3004,6 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1969
3004
|
y: height * (0.2 + rng() * 0.6),
|
|
1970
3005
|
strength: 0.3 + rng() * 0.4
|
|
1971
3006
|
});
|
|
1972
|
-
// Feature E: 1-2 void zones where shapes are sparse (negative space)
|
|
1973
3007
|
const numVoids = Math.floor(rng() * 2) + 1;
|
|
1974
3008
|
const voidZones = [];
|
|
1975
3009
|
for(let v = 0; v < numVoids; v++)voidZones.push({
|
|
@@ -2001,20 +3035,24 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2001
3035
|
}
|
|
2002
3036
|
// Track all placed shapes for density checks and connecting curves
|
|
2003
3037
|
const shapePositions = [];
|
|
3038
|
+
// Hero avoidance radius — shapes near the hero orient toward it
|
|
3039
|
+
let heroCenter = null;
|
|
2004
3040
|
// ── 4b. Hero shape — a dominant focal element ───────────────────
|
|
2005
3041
|
if (archetype.heroShape && rng() < 0.6) {
|
|
2006
3042
|
const heroFocal = focalPoints[0];
|
|
3043
|
+
// Use shape palette hero candidates
|
|
2007
3044
|
const heroPool = [
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
];
|
|
2013
|
-
const heroShape = heroPool.filter((s)=>shapeNames.includes(s))[Math.floor(rng() * heroPool.filter((s)=>shapeNames.includes(s)).length)] || shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
3045
|
+
...shapePalette.primary,
|
|
3046
|
+
...shapePalette.supporting
|
|
3047
|
+
].filter((s)=>(0, $e73976f898150d4d$export$4343b39fe47bd82c)[s]?.heroCandidate && shapeNames.includes(s));
|
|
3048
|
+
const heroShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
2014
3049
|
const heroSize = adjustedMaxSize * (0.8 + rng() * 0.5);
|
|
2015
3050
|
const heroRotation = rng() * 360;
|
|
2016
|
-
const heroFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$
|
|
2017
|
-
const heroStroke = (0, $d016ad53434219a1$export$
|
|
3051
|
+
const heroFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.dominant, rng, 6, 0.05), bgLum), 0.15 + rng() * 0.2);
|
|
3052
|
+
const heroStroke = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.accent, rng, 6, 0.05), bgLum);
|
|
3053
|
+
// Get best style for this hero shape
|
|
3054
|
+
const heroProfile = (0, $e73976f898150d4d$export$4343b39fe47bd82c)[heroShape];
|
|
3055
|
+
const heroStyle = heroProfile ? heroProfile.bestStyles[Math.floor(rng() * heroProfile.bestStyles.length)] : rng() < 0.4 ? "watercolor" : "fill-and-stroke";
|
|
2018
3056
|
ctx.globalAlpha = 0.5 + rng() * 0.2;
|
|
2019
3057
|
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, heroShape, heroFocal.x, heroFocal.y, {
|
|
2020
3058
|
fillColor: heroFill,
|
|
@@ -2025,14 +3063,20 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2025
3063
|
proportionType: "GOLDEN_RATIO",
|
|
2026
3064
|
glowRadius: (12 + rng() * 20) * scaleFactor,
|
|
2027
3065
|
glowColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
2028
|
-
gradientFillEnd: (0, $d016ad53434219a1$export$
|
|
2029
|
-
renderStyle:
|
|
3066
|
+
gradientFillEnd: (0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
3067
|
+
renderStyle: heroStyle,
|
|
2030
3068
|
rng: rng
|
|
2031
3069
|
});
|
|
2032
|
-
|
|
3070
|
+
heroCenter = {
|
|
2033
3071
|
x: heroFocal.x,
|
|
2034
3072
|
y: heroFocal.y,
|
|
2035
3073
|
size: heroSize
|
|
3074
|
+
};
|
|
3075
|
+
shapePositions.push({
|
|
3076
|
+
x: heroFocal.x,
|
|
3077
|
+
y: heroFocal.y,
|
|
3078
|
+
size: heroSize,
|
|
3079
|
+
shape: heroShape
|
|
2036
3080
|
});
|
|
2037
3081
|
}
|
|
2038
3082
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
@@ -2043,41 +3087,52 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2043
3087
|
const numShapes = shapesPerLayer + Math.floor(rng() * shapesPerLayer * 0.3);
|
|
2044
3088
|
const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);
|
|
2045
3089
|
const layerSizeScale = 1 - layer * 0.15;
|
|
2046
|
-
//
|
|
3090
|
+
// Per-layer blend mode
|
|
2047
3091
|
const layerBlend = (0, $c3de8257a8baa3b0$export$7bb7bff4e26fa06b)(rng);
|
|
2048
3092
|
ctx.globalCompositeOperation = layerBlend;
|
|
2049
|
-
//
|
|
3093
|
+
// Per-layer render style bias — prefer archetype styles
|
|
2050
3094
|
const layerRenderStyle = rng() < 0.6 ? archetype.preferredStyles[Math.floor(rng() * archetype.preferredStyles.length)] : (0, $c3de8257a8baa3b0$export$9fd4e64b2acd410e)(rng);
|
|
2051
|
-
//
|
|
2052
|
-
const atmosphericDesat = layerRatio * 0.3;
|
|
3095
|
+
// Atmospheric desaturation for later layers
|
|
3096
|
+
const atmosphericDesat = layerRatio * 0.3;
|
|
2053
3097
|
for(let i = 0; i < numShapes; i++){
|
|
2054
3098
|
// Position from composition mode, then focal bias
|
|
2055
3099
|
const rawPos = $4f72c5a314eddf25$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
2056
3100
|
const [x, y] = applyFocalBias(rawPos.x, rawPos.y);
|
|
2057
|
-
//
|
|
3101
|
+
// Skip shapes in void zones, reduce in dense areas
|
|
2058
3102
|
if ($4f72c5a314eddf25$var$isInVoidZone(x, y, voidZones)) {
|
|
2059
|
-
// 85% chance to skip — allows a few shapes to bleed in
|
|
2060
3103
|
if (rng() < 0.85) continue;
|
|
2061
3104
|
}
|
|
2062
3105
|
if ($4f72c5a314eddf25$var$localDensity(x, y, shapePositions, densityCheckRadius) > maxLocalDensity) {
|
|
2063
|
-
if (rng() < 0.6) continue;
|
|
3106
|
+
if (rng() < 0.6) continue;
|
|
2064
3107
|
}
|
|
2065
|
-
// Weighted shape selection
|
|
2066
|
-
const shape = $4f72c5a314eddf25$var$pickShape(rng, layerRatio, shapeNames);
|
|
2067
3108
|
// Power distribution for size — archetype controls the curve
|
|
2068
3109
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
2069
3110
|
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
3111
|
+
// Size fraction for affinity-aware shape selection
|
|
3112
|
+
const sizeFraction = size / adjustedMaxSize;
|
|
3113
|
+
// Palette-driven shape selection (replaces naive pickShape)
|
|
3114
|
+
const shape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, sizeFraction);
|
|
2070
3115
|
// Flow-field rotation in flow-field mode, random otherwise
|
|
2071
|
-
|
|
2072
|
-
//
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
3116
|
+
let rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
|
|
3117
|
+
// Hero avoidance: shapes near the hero orient toward it
|
|
3118
|
+
if (heroCenter) {
|
|
3119
|
+
const distToHero = Math.hypot(x - heroCenter.x, y - heroCenter.y);
|
|
3120
|
+
const heroInfluence = heroCenter.size * 1.5;
|
|
3121
|
+
if (distToHero < heroInfluence && distToHero > 0) {
|
|
3122
|
+
const angleToHero = Math.atan2(heroCenter.y - y, heroCenter.x - x) * 180 / Math.PI;
|
|
3123
|
+
const blendFactor = 1 - distToHero / heroInfluence;
|
|
3124
|
+
rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
// Positional color from hierarchy + jitter
|
|
3128
|
+
let fillBase = $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, colorHierarchy, rng);
|
|
3129
|
+
const strokeBase = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
3130
|
+
// Desaturate colors on later layers for depth
|
|
2076
3131
|
if (atmosphericDesat > 0) fillBase = (0, $d016ad53434219a1$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
2077
3132
|
// Temperature contrast: shift foreground shapes opposite to background
|
|
2078
3133
|
if (fgTempTarget) fillBase = (0, $d016ad53434219a1$export$51ea55f869b7e0d3)(fillBase, fgTempTarget, 0.15 + layerRatio * 0.1);
|
|
2079
|
-
const fillColor = (0, $d016ad53434219a1$export$
|
|
2080
|
-
const strokeColor = (0, $d016ad53434219a1$export$
|
|
3134
|
+
const fillColor = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(fillBase, rng, 6, 0.05), bgLum);
|
|
3135
|
+
const strokeColor = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
|
|
2081
3136
|
// Semi-transparent fill
|
|
2082
3137
|
const fillAlpha = 0.2 + rng() * 0.5;
|
|
2083
3138
|
const transparentFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, fillAlpha);
|
|
@@ -2091,12 +3146,16 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2091
3146
|
const glowRadius = hasGlow ? (8 + rng() * 20) * scaleFactor : 0;
|
|
2092
3147
|
// Gradient fill on ~30%
|
|
2093
3148
|
const hasGradient = rng() < 0.3;
|
|
2094
|
-
const gradientEnd = hasGradient ? (0, $d016ad53434219a1$export$
|
|
2095
|
-
//
|
|
2096
|
-
const shapeRenderStyle =
|
|
2097
|
-
//
|
|
3149
|
+
const gradientEnd = hasGradient ? (0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1) : undefined;
|
|
3150
|
+
// Affinity-aware render style selection
|
|
3151
|
+
const shapeRenderStyle = (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(shape, layerRenderStyle, rng);
|
|
3152
|
+
// Organic edge jitter — applied via watercolor style on ~15% of shapes
|
|
2098
3153
|
const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
|
|
2099
3154
|
const finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
|
|
3155
|
+
// Consistent light direction — subtle shadow offset
|
|
3156
|
+
const shadowDist = hasGlow ? 0 : size * 0.02;
|
|
3157
|
+
const shadowOffX = shadowDist * Math.cos(lightAngle);
|
|
3158
|
+
const shadowOffY = shadowDist * Math.sin(lightAngle);
|
|
2100
3159
|
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
|
|
2101
3160
|
fillColor: transparentFill,
|
|
2102
3161
|
strokeColor: strokeColor,
|
|
@@ -2104,8 +3163,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2104
3163
|
size: size,
|
|
2105
3164
|
rotation: rotation,
|
|
2106
3165
|
proportionType: "GOLDEN_RATIO",
|
|
2107
|
-
glowRadius: glowRadius,
|
|
2108
|
-
glowColor: hasGlow ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.6) : undefined,
|
|
3166
|
+
glowRadius: glowRadius || (shadowDist > 0 ? shadowDist * 2 : 0),
|
|
3167
|
+
glowColor: hasGlow ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
2109
3168
|
gradientFillEnd: gradientEnd,
|
|
2110
3169
|
renderStyle: finalRenderStyle,
|
|
2111
3170
|
rng: rng
|
|
@@ -2113,18 +3172,51 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2113
3172
|
shapePositions.push({
|
|
2114
3173
|
x: x,
|
|
2115
3174
|
y: y,
|
|
2116
|
-
size: size
|
|
3175
|
+
size: size,
|
|
3176
|
+
shape: shape
|
|
2117
3177
|
});
|
|
2118
|
-
// ── 5b.
|
|
3178
|
+
// ── 5b. Size echo — large shapes spawn trailing smaller copies ──
|
|
3179
|
+
if (size > adjustedMaxSize * 0.5 && rng() < 0.2) {
|
|
3180
|
+
const echoCount = 2 + Math.floor(rng() * 2);
|
|
3181
|
+
const echoAngle = rng() * Math.PI * 2;
|
|
3182
|
+
for(let e = 0; e < echoCount; e++){
|
|
3183
|
+
const echoScale = 0.3 - e * 0.08;
|
|
3184
|
+
const echoDist = size * (0.6 + e * 0.4);
|
|
3185
|
+
const echoX = x + Math.cos(echoAngle) * echoDist;
|
|
3186
|
+
const echoY = y + Math.sin(echoAngle) * echoDist;
|
|
3187
|
+
const echoSize = size * Math.max(0.1, echoScale);
|
|
3188
|
+
if (echoX < 0 || echoX > width || echoY < 0 || echoY > height) continue;
|
|
3189
|
+
ctx.globalAlpha = layerOpacity * (0.4 - e * 0.1);
|
|
3190
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, echoX, echoY, {
|
|
3191
|
+
fillColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, fillAlpha * 0.6),
|
|
3192
|
+
strokeColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(strokeColor, 0.4),
|
|
3193
|
+
strokeWidth: strokeWidth * 0.6,
|
|
3194
|
+
size: echoSize,
|
|
3195
|
+
rotation: rotation + (e + 1) * 15,
|
|
3196
|
+
proportionType: "GOLDEN_RATIO",
|
|
3197
|
+
renderStyle: finalRenderStyle,
|
|
3198
|
+
rng: rng
|
|
3199
|
+
});
|
|
3200
|
+
shapePositions.push({
|
|
3201
|
+
x: echoX,
|
|
3202
|
+
y: echoY,
|
|
3203
|
+
size: echoSize,
|
|
3204
|
+
shape: shape
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
// ── 5c. Recursive nesting ──────────────────────────────────
|
|
2119
3209
|
if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
|
|
2120
3210
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
2121
3211
|
for(let n = 0; n < innerCount; n++){
|
|
2122
|
-
|
|
3212
|
+
// Pick inner shape from palette affinities
|
|
3213
|
+
const innerSizeFraction = size * 0.25 / adjustedMaxSize;
|
|
3214
|
+
const innerShape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, innerSizeFraction);
|
|
2123
3215
|
const innerSize = size * (0.15 + rng() * 0.25);
|
|
2124
3216
|
const innerOffX = (rng() - 0.5) * size * 0.4;
|
|
2125
3217
|
const innerOffY = (rng() - 0.5) * size * 0.4;
|
|
2126
3218
|
const innerRot = rng() * 360;
|
|
2127
|
-
const innerFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$
|
|
3219
|
+
const innerFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1), 0.3 + rng() * 0.4);
|
|
2128
3220
|
ctx.globalAlpha = layerOpacity * 0.7;
|
|
2129
3221
|
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, innerShape, x + innerOffX, y + innerOffY, {
|
|
2130
3222
|
fillColor: innerFill,
|
|
@@ -2133,7 +3225,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2133
3225
|
size: innerSize,
|
|
2134
3226
|
rotation: innerRot,
|
|
2135
3227
|
proportionType: "GOLDEN_RATIO",
|
|
2136
|
-
renderStyle:
|
|
3228
|
+
renderStyle: (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(innerShape, layerRenderStyle, rng),
|
|
2137
3229
|
rng: rng
|
|
2138
3230
|
});
|
|
2139
3231
|
}
|
|
@@ -2142,7 +3234,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2142
3234
|
}
|
|
2143
3235
|
// Reset blend mode for post-processing passes
|
|
2144
3236
|
ctx.globalCompositeOperation = "source-over";
|
|
2145
|
-
// ── 6. Flow-line pass
|
|
3237
|
+
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
2146
3238
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
2147
3239
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
2148
3240
|
for(let i = 0; i < numFlowLines; i++){
|
|
@@ -2151,9 +3243,13 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2151
3243
|
const steps = 30 + Math.floor(rng() * 40);
|
|
2152
3244
|
const stepLen = (3 + rng() * 5) * scaleFactor;
|
|
2153
3245
|
const startWidth = (1 + rng() * 3) * scaleFactor;
|
|
2154
|
-
|
|
3246
|
+
// Variable color: interpolate between two hierarchy colors along the stroke
|
|
3247
|
+
const lineColorStart = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
|
|
3248
|
+
const lineColorEnd = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
|
|
2155
3249
|
const lineAlpha = 0.06 + rng() * 0.1;
|
|
2156
|
-
//
|
|
3250
|
+
// Pressure simulation: sinusoidal width variation
|
|
3251
|
+
const pressureFreq = 2 + rng() * 4;
|
|
3252
|
+
const pressurePhase = rng() * Math.PI * 2;
|
|
2157
3253
|
let prevX = fx;
|
|
2158
3254
|
let prevY = fy;
|
|
2159
3255
|
for(let s = 0; s < steps; s++){
|
|
@@ -2161,37 +3257,61 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2161
3257
|
fx += Math.cos(angle) * stepLen;
|
|
2162
3258
|
fy += Math.sin(angle) * stepLen;
|
|
2163
3259
|
if (fx < 0 || fx > width || fy < 0 || fy > height) break;
|
|
2164
|
-
|
|
2165
|
-
|
|
3260
|
+
const t = s / steps;
|
|
3261
|
+
// Taper + pressure
|
|
3262
|
+
const taper = 1 - t * 0.8;
|
|
3263
|
+
const pressure = 0.6 + 0.4 * Math.sin(t * pressureFreq * Math.PI + pressurePhase);
|
|
2166
3264
|
ctx.globalAlpha = lineAlpha * taper;
|
|
3265
|
+
// Interpolate color along stroke
|
|
3266
|
+
const lineColor = t < 0.5 ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(lineColorStart, 0.4 + t * 0.2) : (0, $d016ad53434219a1$export$f2121afcad3d553f)(lineColorEnd, 0.4 + (1 - t) * 0.2);
|
|
2167
3267
|
ctx.strokeStyle = lineColor;
|
|
2168
|
-
ctx.lineWidth = startWidth * taper;
|
|
3268
|
+
ctx.lineWidth = startWidth * taper * pressure;
|
|
2169
3269
|
ctx.lineCap = "round";
|
|
2170
3270
|
ctx.beginPath();
|
|
2171
3271
|
ctx.moveTo(prevX, prevY);
|
|
2172
3272
|
ctx.lineTo(fx, fy);
|
|
2173
3273
|
ctx.stroke();
|
|
3274
|
+
// Branching: ~12% chance per step to spawn a thinner child stroke
|
|
3275
|
+
if (rng() < 0.12 && s > 5 && s < steps - 10) {
|
|
3276
|
+
const branchAngle = angle + (rng() < 0.5 ? 1 : -1) * (0.3 + rng() * 0.5);
|
|
3277
|
+
let bx = fx;
|
|
3278
|
+
let by = fy;
|
|
3279
|
+
let bPrevX = fx;
|
|
3280
|
+
let bPrevY = fy;
|
|
3281
|
+
const branchSteps = 5 + Math.floor(rng() * 10);
|
|
3282
|
+
const branchWidth = startWidth * taper * 0.4;
|
|
3283
|
+
for(let bs = 0; bs < branchSteps; bs++){
|
|
3284
|
+
const bAngle = branchAngle + (rng() - 0.5) * 0.2;
|
|
3285
|
+
bx += Math.cos(bAngle) * stepLen * 0.8;
|
|
3286
|
+
by += Math.sin(bAngle) * stepLen * 0.8;
|
|
3287
|
+
if (bx < 0 || bx > width || by < 0 || by > height) break;
|
|
3288
|
+
const bTaper = 1 - bs / branchSteps * 0.9;
|
|
3289
|
+
ctx.globalAlpha = lineAlpha * taper * bTaper * 0.6;
|
|
3290
|
+
ctx.lineWidth = branchWidth * bTaper;
|
|
3291
|
+
ctx.beginPath();
|
|
3292
|
+
ctx.moveTo(bPrevX, bPrevY);
|
|
3293
|
+
ctx.lineTo(bx, by);
|
|
3294
|
+
ctx.stroke();
|
|
3295
|
+
bPrevX = bx;
|
|
3296
|
+
bPrevY = by;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
2174
3299
|
prevX = fx;
|
|
2175
3300
|
prevY = fy;
|
|
2176
3301
|
}
|
|
2177
3302
|
}
|
|
2178
3303
|
// ── 6b. Apply symmetry mirroring ─────────────────────────────────
|
|
2179
|
-
// Mirror the rendered content (shapes + flow lines) before post-processing.
|
|
2180
|
-
// Uses ctx.canvas which is available in both Node (@napi-rs/canvas) and browsers.
|
|
2181
3304
|
if (symmetryMode !== "none") {
|
|
2182
3305
|
const canvas = ctx.canvas;
|
|
2183
3306
|
ctx.save();
|
|
2184
3307
|
if (symmetryMode === "bilateral-x" || symmetryMode === "quad") {
|
|
2185
|
-
// Mirror left half onto right half
|
|
2186
3308
|
ctx.save();
|
|
2187
3309
|
ctx.translate(width, 0);
|
|
2188
3310
|
ctx.scale(-1, 1);
|
|
2189
|
-
// Draw the left half (0 to cx) onto the mirrored right side
|
|
2190
3311
|
ctx.drawImage(canvas, 0, 0, Math.ceil(cx), height, 0, 0, Math.ceil(cx), height);
|
|
2191
3312
|
ctx.restore();
|
|
2192
3313
|
}
|
|
2193
3314
|
if (symmetryMode === "bilateral-y" || symmetryMode === "quad") {
|
|
2194
|
-
// Mirror top half onto bottom half
|
|
2195
3315
|
ctx.save();
|
|
2196
3316
|
ctx.translate(0, height);
|
|
2197
3317
|
ctx.scale(1, -1);
|
|
@@ -2214,7 +3334,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2214
3334
|
}
|
|
2215
3335
|
// ── 8. Vignette — darken edges to draw the eye inward ───────────
|
|
2216
3336
|
ctx.globalAlpha = 1;
|
|
2217
|
-
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
3337
|
+
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
2218
3338
|
const vigGrad = ctx.createRadialGradient(cx, cy, Math.min(width, height) * 0.3, cx, cy, bgRadius);
|
|
2219
3339
|
vigGrad.addColorStop(0, "rgba(0,0,0,0)");
|
|
2220
3340
|
vigGrad.addColorStop(0.6, "rgba(0,0,0,0)");
|
|
@@ -2240,13 +3360,57 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2240
3360
|
const cpx = mx + -dy / (dist || 1) * bulge;
|
|
2241
3361
|
const cpy = my + dx / (dist || 1) * bulge;
|
|
2242
3362
|
ctx.globalAlpha = 0.06 + rng() * 0.1;
|
|
2243
|
-
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(
|
|
3363
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
2244
3364
|
ctx.beginPath();
|
|
2245
3365
|
ctx.moveTo(a.x, a.y);
|
|
2246
3366
|
ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);
|
|
2247
3367
|
ctx.stroke();
|
|
2248
3368
|
}
|
|
2249
3369
|
}
|
|
3370
|
+
// ── 10. Post-processing ────────────────────────────────────────
|
|
3371
|
+
// 10a. Color grading — unified tone across the whole image
|
|
3372
|
+
// Apply as a semi-transparent overlay in the grade hue
|
|
3373
|
+
ctx.globalAlpha = colorGrade.intensity * 0.25;
|
|
3374
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
3375
|
+
const gradeHsl = `hsl(${Math.round(colorGrade.hue)}, 40%, 50%)`;
|
|
3376
|
+
ctx.fillStyle = gradeHsl;
|
|
3377
|
+
ctx.fillRect(0, 0, width, height);
|
|
3378
|
+
ctx.globalCompositeOperation = "source-over";
|
|
3379
|
+
// 10b. Chromatic aberration — subtle RGB channel offset at edges
|
|
3380
|
+
// Only apply for neon/cosmic/ethereal archetypes where it fits
|
|
3381
|
+
const chromaArchetypes = [
|
|
3382
|
+
"neon-glow",
|
|
3383
|
+
"cosmic",
|
|
3384
|
+
"ethereal"
|
|
3385
|
+
];
|
|
3386
|
+
if (chromaArchetypes.includes(archetype.name)) {
|
|
3387
|
+
const chromaOffset = Math.ceil(2 * scaleFactor);
|
|
3388
|
+
const canvas = ctx.canvas;
|
|
3389
|
+
// Shift red channel slightly
|
|
3390
|
+
ctx.globalAlpha = 0.03;
|
|
3391
|
+
ctx.globalCompositeOperation = "screen";
|
|
3392
|
+
ctx.drawImage(canvas, chromaOffset, 0, width, height, 0, 0, width, height);
|
|
3393
|
+
// Shift blue channel opposite
|
|
3394
|
+
ctx.drawImage(canvas, -chromaOffset, 0, width, height, 0, 0, width, height);
|
|
3395
|
+
ctx.globalCompositeOperation = "source-over";
|
|
3396
|
+
}
|
|
3397
|
+
// 10c. Bloom — soft glow on bright areas for neon/cosmic archetypes
|
|
3398
|
+
const bloomArchetypes = [
|
|
3399
|
+
"neon-glow",
|
|
3400
|
+
"cosmic"
|
|
3401
|
+
];
|
|
3402
|
+
if (bloomArchetypes.includes(archetype.name)) {
|
|
3403
|
+
const canvas = ctx.canvas;
|
|
3404
|
+
ctx.globalAlpha = 0.08;
|
|
3405
|
+
ctx.globalCompositeOperation = "screen";
|
|
3406
|
+
// Draw the image slightly scaled up and blurred via shadow
|
|
3407
|
+
ctx.save();
|
|
3408
|
+
ctx.shadowBlur = 30 * scaleFactor;
|
|
3409
|
+
ctx.shadowColor = "rgba(255,255,255,0.3)";
|
|
3410
|
+
ctx.drawImage(canvas, 0, 0, width, height);
|
|
3411
|
+
ctx.restore();
|
|
3412
|
+
ctx.globalCompositeOperation = "source-over";
|
|
3413
|
+
}
|
|
2250
3414
|
ctx.globalAlpha = 1;
|
|
2251
3415
|
}
|
|
2252
3416
|
|