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