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