git-hash-art 0.7.0 → 0.9.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 +323 -270
- package/CHANGELOG.md +18 -0
- package/bin/cli.js +17 -14
- package/bin/generateExamples.js +6 -14
- package/bin/generateVersionComparison.js +353 -0
- package/dist/browser.js +2398 -225
- package/dist/browser.js.map +1 -1
- package/dist/main.js +2398 -225
- package/dist/main.js.map +1 -1
- package/dist/module.js +2398 -225
- package/dist/module.js.map +1 -1
- package/package.json +2 -1
- package/src/lib/archetypes.ts +119 -0
- package/src/lib/canvas/colors.ts +110 -2
- package/src/lib/canvas/draw.ts +359 -9
- package/src/lib/canvas/shapes/affinity.ts +624 -0
- package/src/lib/canvas/shapes/procedural.ts +395 -32
- package/src/lib/render.ts +531 -155
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;
|
|
@@ -436,6 +502,31 @@ function $9d614e7d77fc2947$export$90ad0e6170cf6af5(fgHex, bgLuminance, minContra
|
|
|
436
502
|
return $9d614e7d77fc2947$var$hslToHex(h, targetS, targetL);
|
|
437
503
|
}
|
|
438
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
|
+
}
|
|
439
530
|
|
|
440
531
|
|
|
441
532
|
|
|
@@ -1226,35 +1317,32 @@ const $d63629e16208c310$export$c2fc138f94dd4b2a = {
|
|
|
1226
1317
|
*/ const $a7241acce45d5513$export$580f80cfb9de73bc = (ctx, size, config)=>{
|
|
1227
1318
|
const rng = config?.rng ?? Math.random;
|
|
1228
1319
|
const r = size / 2;
|
|
1229
|
-
const numPoints = 5 + Math.floor(rng() * 5);
|
|
1320
|
+
const numPoints = 5 + Math.floor(rng() * 5);
|
|
1230
1321
|
const points = [];
|
|
1231
1322
|
for(let i = 0; i < numPoints; i++){
|
|
1232
1323
|
const angle = i / numPoints * Math.PI * 2;
|
|
1233
|
-
const jitter = 0.5 + rng() * 0.5;
|
|
1324
|
+
const jitter = 0.5 + rng() * 0.5;
|
|
1234
1325
|
points.push({
|
|
1235
1326
|
x: Math.cos(angle) * r * jitter,
|
|
1236
1327
|
y: Math.sin(angle) * r * jitter
|
|
1237
1328
|
});
|
|
1238
1329
|
}
|
|
1239
1330
|
ctx.beginPath();
|
|
1240
|
-
// Start at midpoint between last and first point
|
|
1241
1331
|
const last = points[points.length - 1];
|
|
1242
1332
|
const first = points[0];
|
|
1243
1333
|
ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
|
|
1244
1334
|
for(let i = 0; i < numPoints; i++){
|
|
1245
1335
|
const curr = points[i];
|
|
1246
1336
|
const next = points[(i + 1) % numPoints];
|
|
1247
|
-
|
|
1248
|
-
const midY = (curr.y + next.y) / 2;
|
|
1249
|
-
ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
|
|
1337
|
+
ctx.quadraticCurveTo(curr.x, curr.y, (curr.x + next.x) / 2, (curr.y + next.y) / 2);
|
|
1250
1338
|
}
|
|
1251
1339
|
ctx.closePath();
|
|
1252
1340
|
};
|
|
1253
1341
|
const $a7241acce45d5513$export$7a6094023f0902a6 = (ctx, size, config)=>{
|
|
1254
1342
|
const rng = config?.rng ?? Math.random;
|
|
1255
1343
|
const r = size / 2;
|
|
1256
|
-
const sides = 3 + Math.floor(rng() * 10);
|
|
1257
|
-
const jitterAmount = 0.1 + rng() * 0.4;
|
|
1344
|
+
const sides = 3 + Math.floor(rng() * 10);
|
|
1345
|
+
const jitterAmount = 0.1 + rng() * 0.4;
|
|
1258
1346
|
ctx.beginPath();
|
|
1259
1347
|
for(let i = 0; i < sides; i++){
|
|
1260
1348
|
const angle = i / sides * Math.PI * 2 - Math.PI / 2;
|
|
@@ -1269,10 +1357,9 @@ const $a7241acce45d5513$export$7a6094023f0902a6 = (ctx, size, config)=>{
|
|
|
1269
1357
|
const $a7241acce45d5513$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
|
|
1270
1358
|
const rng = config?.rng ?? Math.random;
|
|
1271
1359
|
const r = size / 2;
|
|
1272
|
-
|
|
1273
|
-
const
|
|
1274
|
-
const
|
|
1275
|
-
const phase = rng() * Math.PI; // phase offset
|
|
1360
|
+
const freqA = 1 + Math.floor(rng() * 5);
|
|
1361
|
+
const freqB = 1 + Math.floor(rng() * 5);
|
|
1362
|
+
const phase = rng() * Math.PI;
|
|
1276
1363
|
const steps = 120;
|
|
1277
1364
|
ctx.beginPath();
|
|
1278
1365
|
for(let i = 0; i <= steps; i++){
|
|
@@ -1287,7 +1374,6 @@ const $a7241acce45d5513$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
|
|
|
1287
1374
|
const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
1288
1375
|
const rng = config?.rng ?? Math.random;
|
|
1289
1376
|
const r = size / 2;
|
|
1290
|
-
// Exponent range: 0.3 (spiky astroid) to 5 (rounded rectangle)
|
|
1291
1377
|
const n = 0.3 + rng() * 4.7;
|
|
1292
1378
|
const steps = 120;
|
|
1293
1379
|
ctx.beginPath();
|
|
@@ -1295,7 +1381,6 @@ const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
|
1295
1381
|
const t = i / steps * Math.PI * 2;
|
|
1296
1382
|
const cosT = Math.cos(t);
|
|
1297
1383
|
const sinT = Math.sin(t);
|
|
1298
|
-
// Superellipse parametric form
|
|
1299
1384
|
const x = Math.sign(cosT) * Math.pow(Math.abs(cosT), 2 / n) * r;
|
|
1300
1385
|
const y = Math.sign(sinT) * Math.pow(Math.abs(sinT), 2 / n) * r;
|
|
1301
1386
|
if (i === 0) ctx.moveTo(x, y);
|
|
@@ -1306,11 +1391,9 @@ const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
|
1306
1391
|
const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
1307
1392
|
const rng = config?.rng ?? Math.random;
|
|
1308
1393
|
const scale = size / 2;
|
|
1309
|
-
// R = outer radius, r = inner radius, d = pen distance from inner center
|
|
1310
1394
|
const R = 1;
|
|
1311
|
-
const r = 0.2 + rng() * 0.6;
|
|
1312
|
-
const d = 0.3 + rng() * 0.7;
|
|
1313
|
-
// Number of full rotations needed to close the curve
|
|
1395
|
+
const r = 0.2 + rng() * 0.6;
|
|
1396
|
+
const d = 0.3 + rng() * 0.7;
|
|
1314
1397
|
const gcd = (a, b)=>{
|
|
1315
1398
|
const ai = Math.round(a * 1000);
|
|
1316
1399
|
const bi = Math.round(b * 1000);
|
|
@@ -1318,7 +1401,7 @@ const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
|
1318
1401
|
return g(ai, bi) / 1000;
|
|
1319
1402
|
};
|
|
1320
1403
|
const period = r / gcd(R, r);
|
|
1321
|
-
const maxT = Math.min(period, 10) * Math.PI * 2;
|
|
1404
|
+
const maxT = Math.min(period, 10) * Math.PI * 2;
|
|
1322
1405
|
const steps = Math.min(600, Math.floor(maxT * 20));
|
|
1323
1406
|
ctx.beginPath();
|
|
1324
1407
|
for(let i = 0; i <= steps; i++){
|
|
@@ -1333,9 +1416,9 @@ const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
|
1333
1416
|
const $a7241acce45d5513$export$7608ccd03bfb705d = (ctx, size, config)=>{
|
|
1334
1417
|
const rng = config?.rng ?? Math.random;
|
|
1335
1418
|
const r = size / 2;
|
|
1336
|
-
const rings = 2 + Math.floor(rng() * 4);
|
|
1337
|
-
const freq = 3 + Math.floor(rng() * 12);
|
|
1338
|
-
const amp = 0.05 + rng() * 0.15;
|
|
1419
|
+
const rings = 2 + Math.floor(rng() * 4);
|
|
1420
|
+
const freq = 3 + Math.floor(rng() * 12);
|
|
1421
|
+
const amp = 0.05 + rng() * 0.15;
|
|
1339
1422
|
ctx.beginPath();
|
|
1340
1423
|
for(let ring = 0; ring < rings; ring++){
|
|
1341
1424
|
const baseR = r * (0.3 + ring / rings * 0.7);
|
|
@@ -1353,7 +1436,7 @@ const $a7241acce45d5513$export$7608ccd03bfb705d = (ctx, size, config)=>{
|
|
|
1353
1436
|
const $a7241acce45d5513$export$11a377e7498bb523 = (ctx, size, config)=>{
|
|
1354
1437
|
const rng = config?.rng ?? Math.random;
|
|
1355
1438
|
const r = size / 2;
|
|
1356
|
-
const k = 2 + Math.floor(rng() * 6);
|
|
1439
|
+
const k = 2 + Math.floor(rng() * 6);
|
|
1357
1440
|
const steps = 200;
|
|
1358
1441
|
ctx.beginPath();
|
|
1359
1442
|
for(let i = 0; i <= steps; i++){
|
|
@@ -1366,6 +1449,308 @@ const $a7241acce45d5513$export$11a377e7498bb523 = (ctx, size, config)=>{
|
|
|
1366
1449
|
}
|
|
1367
1450
|
ctx.closePath();
|
|
1368
1451
|
};
|
|
1452
|
+
const $a7241acce45d5513$export$76b6526575ea179b = (ctx, size, config)=>{
|
|
1453
|
+
const rng = config?.rng ?? Math.random;
|
|
1454
|
+
const r = size / 2;
|
|
1455
|
+
const shardCount = 4 + Math.floor(rng() * 5); // 4-8 shards
|
|
1456
|
+
ctx.beginPath();
|
|
1457
|
+
for(let s = 0; s < shardCount; s++){
|
|
1458
|
+
const baseAngle = s / shardCount * Math.PI * 2 + (rng() - 0.5) * 0.3;
|
|
1459
|
+
const dist = r * (0.15 + rng() * 0.35);
|
|
1460
|
+
const cx = Math.cos(baseAngle) * dist;
|
|
1461
|
+
const cy = Math.sin(baseAngle) * dist;
|
|
1462
|
+
const shardSize = r * (0.2 + rng() * 0.4);
|
|
1463
|
+
const verts = 3 + Math.floor(rng() * 3); // 3-5 vertices per shard
|
|
1464
|
+
const shardAngleOffset = rng() * Math.PI * 2;
|
|
1465
|
+
for(let v = 0; v < verts; v++){
|
|
1466
|
+
const angle = shardAngleOffset + v / verts * Math.PI * 2;
|
|
1467
|
+
// Elongate shards along their radial direction
|
|
1468
|
+
const stretch = v % 2 === 0 ? 1.0 : 0.3 + rng() * 0.4;
|
|
1469
|
+
const px = cx + Math.cos(angle) * shardSize * stretch;
|
|
1470
|
+
const py = cy + Math.sin(angle) * shardSize * stretch;
|
|
1471
|
+
if (v === 0) ctx.moveTo(px, py);
|
|
1472
|
+
else ctx.lineTo(px, py);
|
|
1473
|
+
}
|
|
1474
|
+
ctx.closePath();
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
const $a7241acce45d5513$export$ed9ff98da5b05073 = (ctx, size, config)=>{
|
|
1478
|
+
const rng = config?.rng ?? Math.random;
|
|
1479
|
+
const r = size / 2;
|
|
1480
|
+
const edgeCount = 5 + Math.floor(rng() * 4); // 5-8 edges
|
|
1481
|
+
const points = [];
|
|
1482
|
+
// Generate edge midpoints at varying distances
|
|
1483
|
+
for(let i = 0; i < edgeCount; i++){
|
|
1484
|
+
const angle = i / edgeCount * Math.PI * 2 + (rng() - 0.5) * 0.4;
|
|
1485
|
+
const dist = r * (0.6 + rng() * 0.4);
|
|
1486
|
+
points.push({
|
|
1487
|
+
angle: angle,
|
|
1488
|
+
x: Math.cos(angle) * dist,
|
|
1489
|
+
y: Math.sin(angle) * dist
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
// Sort by angle for proper winding
|
|
1493
|
+
points.sort((a, b)=>a.angle - b.angle);
|
|
1494
|
+
ctx.beginPath();
|
|
1495
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1496
|
+
for(let i = 1; i < points.length; i++)ctx.lineTo(points[i].x, points[i].y);
|
|
1497
|
+
ctx.closePath();
|
|
1498
|
+
};
|
|
1499
|
+
const $a7241acce45d5513$export$e0452d9a794fe7e5 = (ctx, size, config)=>{
|
|
1500
|
+
const rng = config?.rng ?? Math.random;
|
|
1501
|
+
const r = size / 2;
|
|
1502
|
+
const biteSize = 0.6 + rng() * 0.3; // 60-90% of radius
|
|
1503
|
+
const biteOffset = r * (0.3 + rng() * 0.4);
|
|
1504
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
1505
|
+
// Outer circle
|
|
1506
|
+
ctx.beginPath();
|
|
1507
|
+
ctx.arc(0, 0, r, 0, Math.PI * 2);
|
|
1508
|
+
// Subtract inner circle using even-odd rule
|
|
1509
|
+
const bx = Math.cos(biteAngle) * biteOffset;
|
|
1510
|
+
const by = Math.sin(biteAngle) * biteOffset;
|
|
1511
|
+
// Draw inner circle counter-clockwise for subtraction
|
|
1512
|
+
ctx.moveTo(bx + r * biteSize, by);
|
|
1513
|
+
ctx.arc(bx, by, r * biteSize, 0, Math.PI * 2, true);
|
|
1514
|
+
};
|
|
1515
|
+
const $a7241acce45d5513$export$38bfe5eb52137e01 = (ctx, size, config)=>{
|
|
1516
|
+
const rng = config?.rng ?? Math.random;
|
|
1517
|
+
const r = size / 2;
|
|
1518
|
+
const segments = 12 + Math.floor(rng() * 8);
|
|
1519
|
+
const startAngle = rng() * Math.PI * 2;
|
|
1520
|
+
const curvature = (rng() - 0.5) * 0.4;
|
|
1521
|
+
// Build spine points
|
|
1522
|
+
const spine = [];
|
|
1523
|
+
let angle = startAngle;
|
|
1524
|
+
let px = 0, py = 0;
|
|
1525
|
+
for(let i = 0; i <= segments; i++){
|
|
1526
|
+
spine.push({
|
|
1527
|
+
x: px,
|
|
1528
|
+
y: py
|
|
1529
|
+
});
|
|
1530
|
+
const stepLen = r / segments * (1.5 + rng() * 0.5);
|
|
1531
|
+
angle += curvature + (rng() - 0.5) * 0.6;
|
|
1532
|
+
px += Math.cos(angle) * stepLen;
|
|
1533
|
+
py += Math.sin(angle) * stepLen;
|
|
1534
|
+
}
|
|
1535
|
+
// Build tapered outline by offsetting perpendicular to spine
|
|
1536
|
+
ctx.beginPath();
|
|
1537
|
+
const leftSide = [];
|
|
1538
|
+
const rightSide = [];
|
|
1539
|
+
for(let i = 0; i < spine.length; i++){
|
|
1540
|
+
const t = i / (spine.length - 1);
|
|
1541
|
+
const width = r * 0.12 * (1 - t * 0.9); // taper from thick to thin
|
|
1542
|
+
const next = spine[Math.min(i + 1, spine.length - 1)];
|
|
1543
|
+
const dx = next.x - spine[i].x;
|
|
1544
|
+
const dy = next.y - spine[i].y;
|
|
1545
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1546
|
+
const nx = -dy / len;
|
|
1547
|
+
const ny = dx / len;
|
|
1548
|
+
leftSide.push({
|
|
1549
|
+
x: spine[i].x + nx * width,
|
|
1550
|
+
y: spine[i].y + ny * width
|
|
1551
|
+
});
|
|
1552
|
+
rightSide.push({
|
|
1553
|
+
x: spine[i].x - nx * width,
|
|
1554
|
+
y: spine[i].y - ny * width
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
ctx.moveTo(leftSide[0].x, leftSide[0].y);
|
|
1558
|
+
for(let i = 1; i < leftSide.length; i++)ctx.lineTo(leftSide[i].x, leftSide[i].y);
|
|
1559
|
+
for(let i = rightSide.length - 1; i >= 0; i--)ctx.lineTo(rightSide[i].x, rightSide[i].y);
|
|
1560
|
+
ctx.closePath();
|
|
1561
|
+
};
|
|
1562
|
+
const $a7241acce45d5513$export$105caa8cfd63c422 = (ctx, size, config)=>{
|
|
1563
|
+
const rng = config?.rng ?? Math.random;
|
|
1564
|
+
const r = size / 2;
|
|
1565
|
+
const lobeCount = 4 + Math.floor(rng() * 4); // 4-7 lobes
|
|
1566
|
+
const spineAngle = rng() * Math.PI * 2;
|
|
1567
|
+
const spineLen = r * 0.6;
|
|
1568
|
+
ctx.beginPath();
|
|
1569
|
+
for(let i = 0; i < lobeCount; i++){
|
|
1570
|
+
const t = i / (lobeCount - 1) - 0.5; // -0.5 to 0.5
|
|
1571
|
+
const sx = Math.cos(spineAngle) * spineLen * t;
|
|
1572
|
+
const sy = Math.sin(spineAngle) * spineLen * t;
|
|
1573
|
+
// Offset perpendicular for cloud shape
|
|
1574
|
+
const perpAngle = spineAngle + Math.PI / 2;
|
|
1575
|
+
const perpOff = (rng() - 0.3) * r * 0.3;
|
|
1576
|
+
const cx = sx + Math.cos(perpAngle) * perpOff;
|
|
1577
|
+
const cy = sy + Math.sin(perpAngle) * perpOff;
|
|
1578
|
+
const lobeR = r * (0.25 + rng() * 0.2);
|
|
1579
|
+
ctx.moveTo(cx + lobeR, cy);
|
|
1580
|
+
ctx.arc(cx, cy, lobeR, 0, Math.PI * 2);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
const $a7241acce45d5513$export$e181e5bd3c539569 = (ctx, size, config)=>{
|
|
1584
|
+
const rng = config?.rng ?? Math.random;
|
|
1585
|
+
const r = size / 2;
|
|
1586
|
+
const spikeCount = 8 + Math.floor(rng() * 8); // 8-15 spikes
|
|
1587
|
+
const points = [];
|
|
1588
|
+
for(let i = 0; i < spikeCount; i++){
|
|
1589
|
+
const angle = i / spikeCount * Math.PI * 2;
|
|
1590
|
+
const isSpike = i % 2 === 0;
|
|
1591
|
+
const dist = isSpike ? r * (0.5 + rng() * 0.5 // spikes reach 50-100% of radius
|
|
1592
|
+
) : r * (0.15 + rng() * 0.2); // valleys at 15-35%
|
|
1593
|
+
points.push({
|
|
1594
|
+
x: Math.cos(angle) * dist,
|
|
1595
|
+
y: Math.sin(angle) * dist
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
ctx.beginPath();
|
|
1599
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1600
|
+
for(let i = 0; i < points.length; i++){
|
|
1601
|
+
const curr = points[i];
|
|
1602
|
+
const next = points[(i + 1) % points.length];
|
|
1603
|
+
const cpx = (curr.x + next.x) / 2 + (rng() - 0.5) * r * 0.15;
|
|
1604
|
+
const cpy = (curr.y + next.y) / 2 + (rng() - 0.5) * r * 0.15;
|
|
1605
|
+
ctx.quadraticCurveTo(cpx, cpy, next.x, next.y);
|
|
1606
|
+
}
|
|
1607
|
+
ctx.closePath();
|
|
1608
|
+
};
|
|
1609
|
+
const $a7241acce45d5513$export$155b4780b4c6bb7b = (ctx, size, config)=>{
|
|
1610
|
+
const rng = config?.rng ?? Math.random;
|
|
1611
|
+
const r = size / 2;
|
|
1612
|
+
const subdivisions = 1 + Math.floor(rng() * 3); // 1-3
|
|
1613
|
+
// Start with icosahedron vertices projected to 2D
|
|
1614
|
+
const baseVerts = 6 + subdivisions * 4;
|
|
1615
|
+
const points = [];
|
|
1616
|
+
for(let i = 0; i < baseVerts; i++){
|
|
1617
|
+
const angle = i / baseVerts * Math.PI * 2;
|
|
1618
|
+
const ring = i % 2 === 0 ? 1.0 : 0.5 + rng() * 0.3;
|
|
1619
|
+
points.push({
|
|
1620
|
+
x: Math.cos(angle) * r * ring,
|
|
1621
|
+
y: Math.sin(angle) * r * ring
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
ctx.beginPath();
|
|
1625
|
+
// Draw triangulated mesh — connect each point to neighbors and center
|
|
1626
|
+
for(let i = 0; i < points.length; i++){
|
|
1627
|
+
const next = points[(i + 1) % points.length];
|
|
1628
|
+
ctx.moveTo(points[i].x, points[i].y);
|
|
1629
|
+
ctx.lineTo(next.x, next.y);
|
|
1630
|
+
// Connect to center
|
|
1631
|
+
ctx.moveTo(points[i].x, points[i].y);
|
|
1632
|
+
ctx.lineTo(0, 0);
|
|
1633
|
+
// Cross-connect to create triangulation
|
|
1634
|
+
if (i % 2 === 0 && i + 2 < points.length) {
|
|
1635
|
+
ctx.moveTo(points[i].x, points[i].y);
|
|
1636
|
+
ctx.lineTo(points[i + 2].x, points[i + 2].y);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
// Outer ring
|
|
1640
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1641
|
+
for(let i = 1; i < points.length; i++)ctx.lineTo(points[i].x, points[i].y);
|
|
1642
|
+
ctx.closePath();
|
|
1643
|
+
};
|
|
1644
|
+
const $a7241acce45d5513$export$9a7e648f11155172 = (ctx, size, config)=>{
|
|
1645
|
+
const rng = config?.rng ?? Math.random;
|
|
1646
|
+
const r = size / 2;
|
|
1647
|
+
const phi = (1 + Math.sqrt(5)) / 2; // golden ratio
|
|
1648
|
+
const isKite = rng() < 0.5;
|
|
1649
|
+
ctx.beginPath();
|
|
1650
|
+
if (isKite) {
|
|
1651
|
+
// Kite: two golden triangles joined at base
|
|
1652
|
+
const topY = -r;
|
|
1653
|
+
const bottomY = r * (1 / phi);
|
|
1654
|
+
const midY = r * (1 / phi - 1) * 0.3;
|
|
1655
|
+
const wingX = r * 0.6;
|
|
1656
|
+
ctx.moveTo(0, topY);
|
|
1657
|
+
ctx.lineTo(wingX, midY);
|
|
1658
|
+
ctx.lineTo(0, bottomY);
|
|
1659
|
+
ctx.lineTo(-wingX, midY);
|
|
1660
|
+
} else {
|
|
1661
|
+
// Dart: concave quadrilateral
|
|
1662
|
+
const topY = -r;
|
|
1663
|
+
const bottomY = r * 0.3;
|
|
1664
|
+
const midY = -r * 0.1;
|
|
1665
|
+
const wingX = r * 0.5;
|
|
1666
|
+
ctx.moveTo(0, topY);
|
|
1667
|
+
ctx.lineTo(wingX, midY);
|
|
1668
|
+
ctx.lineTo(0, bottomY);
|
|
1669
|
+
ctx.lineTo(-wingX, midY);
|
|
1670
|
+
}
|
|
1671
|
+
ctx.closePath();
|
|
1672
|
+
};
|
|
1673
|
+
const $a7241acce45d5513$export$1fc0aedbabd73399 = (ctx, size, config)=>{
|
|
1674
|
+
const r = size / 2;
|
|
1675
|
+
// Vertices of equilateral triangle
|
|
1676
|
+
const verts = [];
|
|
1677
|
+
for(let i = 0; i < 3; i++){
|
|
1678
|
+
const angle = i / 3 * Math.PI * 2 - Math.PI / 2;
|
|
1679
|
+
verts.push({
|
|
1680
|
+
x: Math.cos(angle) * r * 0.7,
|
|
1681
|
+
y: Math.sin(angle) * r * 0.7
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
// Side length = distance between vertices
|
|
1685
|
+
const sideLen = Math.hypot(verts[1].x - verts[0].x, verts[1].y - verts[0].y);
|
|
1686
|
+
ctx.beginPath();
|
|
1687
|
+
for(let i = 0; i < 3; i++){
|
|
1688
|
+
const from = verts[(i + 1) % 3];
|
|
1689
|
+
const to = verts[(i + 2) % 3];
|
|
1690
|
+
const center = verts[i];
|
|
1691
|
+
const startAngle = Math.atan2(from.y - center.y, from.x - center.x);
|
|
1692
|
+
const endAngle = Math.atan2(to.y - center.y, to.x - center.x);
|
|
1693
|
+
if (i === 0) ctx.moveTo(from.x, from.y);
|
|
1694
|
+
ctx.arc(center.x, center.y, sideLen, startAngle, endAngle);
|
|
1695
|
+
}
|
|
1696
|
+
ctx.closePath();
|
|
1697
|
+
};
|
|
1698
|
+
const $a7241acce45d5513$export$ef7b5e0c19a21fd1 = (ctx, size, config)=>{
|
|
1699
|
+
const rng = config?.rng ?? Math.random;
|
|
1700
|
+
const r = size / 2;
|
|
1701
|
+
const dotCount = 15 + Math.floor(rng() * 25); // 15-39 dots
|
|
1702
|
+
const clusterTightness = 0.3 + rng() * 0.5;
|
|
1703
|
+
ctx.beginPath();
|
|
1704
|
+
for(let i = 0; i < dotCount; i++){
|
|
1705
|
+
// Gaussian-ish distribution via Box-Muller approximation
|
|
1706
|
+
const u1 = Math.max(0.001, rng());
|
|
1707
|
+
const u2 = rng();
|
|
1708
|
+
const mag = Math.sqrt(-2 * Math.log(u1)) * clusterTightness;
|
|
1709
|
+
const angle = u2 * Math.PI * 2;
|
|
1710
|
+
const dx = Math.cos(angle) * mag * r;
|
|
1711
|
+
const dy = Math.sin(angle) * mag * r;
|
|
1712
|
+
const dotR = r * (0.02 + rng() * 0.04);
|
|
1713
|
+
ctx.moveTo(dx + dotR, dy);
|
|
1714
|
+
ctx.arc(dx, dy, dotR, 0, Math.PI * 2);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
const $a7241acce45d5513$export$f15df8ab60dfcc9a = (ctx, size, config)=>{
|
|
1718
|
+
const rng = config?.rng ?? Math.random;
|
|
1719
|
+
const r = size / 2;
|
|
1720
|
+
const angle1 = rng() * Math.PI;
|
|
1721
|
+
const angle2 = angle1 + Math.PI / 2 + (rng() - 0.5) * 0.3;
|
|
1722
|
+
const spacing = r * (0.08 + rng() * 0.08);
|
|
1723
|
+
const hasCross = rng() < 0.6;
|
|
1724
|
+
// Draw bounding shape (ellipse)
|
|
1725
|
+
const rx = r * (0.7 + rng() * 0.3);
|
|
1726
|
+
const ry = r * (0.5 + rng() * 0.3);
|
|
1727
|
+
// Outer boundary
|
|
1728
|
+
ctx.beginPath();
|
|
1729
|
+
ctx.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2);
|
|
1730
|
+
// Hatch lines clipped to the ellipse
|
|
1731
|
+
const cos1 = Math.cos(angle1);
|
|
1732
|
+
const sin1 = Math.sin(angle1);
|
|
1733
|
+
for(let d = -r; d <= r; d += spacing){
|
|
1734
|
+
const lx1 = d * cos1 - r * sin1;
|
|
1735
|
+
const ly1 = d * sin1 + r * cos1;
|
|
1736
|
+
const lx2 = d * cos1 + r * sin1;
|
|
1737
|
+
const ly2 = d * sin1 - r * cos1;
|
|
1738
|
+
ctx.moveTo(lx1, ly1);
|
|
1739
|
+
ctx.lineTo(lx2, ly2);
|
|
1740
|
+
}
|
|
1741
|
+
if (hasCross) {
|
|
1742
|
+
const cos2 = Math.cos(angle2);
|
|
1743
|
+
const sin2 = Math.sin(angle2);
|
|
1744
|
+
for(let d = -r; d <= r; d += spacing * 1.3){
|
|
1745
|
+
const lx1 = d * cos2 - r * sin2;
|
|
1746
|
+
const ly1 = d * sin2 + r * cos2;
|
|
1747
|
+
const lx2 = d * cos2 + r * sin2;
|
|
1748
|
+
const ly2 = d * sin2 - r * cos2;
|
|
1749
|
+
ctx.moveTo(lx1, ly1);
|
|
1750
|
+
ctx.lineTo(lx2, ly2);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1369
1754
|
const $a7241acce45d5513$export$40cfb4c637f2fbb5 = {
|
|
1370
1755
|
blob: $a7241acce45d5513$export$580f80cfb9de73bc,
|
|
1371
1756
|
ngon: $a7241acce45d5513$export$7a6094023f0902a6,
|
|
@@ -1373,7 +1758,18 @@ const $a7241acce45d5513$export$40cfb4c637f2fbb5 = {
|
|
|
1373
1758
|
superellipse: $a7241acce45d5513$export$1db9219b4f34658c,
|
|
1374
1759
|
spirograph: $a7241acce45d5513$export$b027c64d22b01985,
|
|
1375
1760
|
waveRing: $a7241acce45d5513$export$7608ccd03bfb705d,
|
|
1376
|
-
rose: $a7241acce45d5513$export$11a377e7498bb523
|
|
1761
|
+
rose: $a7241acce45d5513$export$11a377e7498bb523,
|
|
1762
|
+
shardField: $a7241acce45d5513$export$76b6526575ea179b,
|
|
1763
|
+
voronoiCell: $a7241acce45d5513$export$ed9ff98da5b05073,
|
|
1764
|
+
crescent: $a7241acce45d5513$export$e0452d9a794fe7e5,
|
|
1765
|
+
tendril: $a7241acce45d5513$export$38bfe5eb52137e01,
|
|
1766
|
+
cloudForm: $a7241acce45d5513$export$105caa8cfd63c422,
|
|
1767
|
+
inkSplat: $a7241acce45d5513$export$e181e5bd3c539569,
|
|
1768
|
+
geodesicDome: $a7241acce45d5513$export$155b4780b4c6bb7b,
|
|
1769
|
+
penroseTile: $a7241acce45d5513$export$9a7e648f11155172,
|
|
1770
|
+
reuleauxTriangle: $a7241acce45d5513$export$1fc0aedbabd73399,
|
|
1771
|
+
dotCluster: $a7241acce45d5513$export$ef7b5e0c19a21fd1,
|
|
1772
|
+
crosshatchPatch: $a7241acce45d5513$export$f15df8ab60dfcc9a
|
|
1377
1773
|
};
|
|
1378
1774
|
|
|
1379
1775
|
|
|
@@ -1408,7 +1804,13 @@ const $9beb8f41637c29fd$var$RENDER_STYLES = [
|
|
|
1408
1804
|
"dashed",
|
|
1409
1805
|
"watercolor",
|
|
1410
1806
|
"hatched",
|
|
1411
|
-
"incomplete"
|
|
1807
|
+
"incomplete",
|
|
1808
|
+
"stipple",
|
|
1809
|
+
"stencil",
|
|
1810
|
+
"noise-grain",
|
|
1811
|
+
"wood-grain",
|
|
1812
|
+
"marble-vein",
|
|
1813
|
+
"fabric-weave"
|
|
1412
1814
|
];
|
|
1413
1815
|
function $9beb8f41637c29fd$export$9fd4e64b2acd410e(rng) {
|
|
1414
1816
|
return $9beb8f41637c29fd$var$RENDER_STYLES[Math.floor(rng() * $9beb8f41637c29fd$var$RENDER_STYLES.length)];
|
|
@@ -1466,23 +1868,50 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1466
1868
|
break;
|
|
1467
1869
|
case "watercolor":
|
|
1468
1870
|
{
|
|
1469
|
-
//
|
|
1470
|
-
const passes =
|
|
1871
|
+
// Improved watercolor: edge darkening + radial bleed + layered washes
|
|
1872
|
+
const passes = 4 + (rng ? Math.floor(rng() * 2) : 0);
|
|
1471
1873
|
const savedAlpha = ctx.globalAlpha;
|
|
1472
|
-
|
|
1874
|
+
// Pass 1: Base wash — large, soft fill at low opacity
|
|
1875
|
+
ctx.globalAlpha = savedAlpha * 0.15;
|
|
1876
|
+
ctx.save();
|
|
1877
|
+
const baseScale = 1.08 + (rng ? rng() * 0.04 : 0);
|
|
1878
|
+
ctx.scale(baseScale, baseScale);
|
|
1879
|
+
ctx.fill();
|
|
1880
|
+
ctx.restore();
|
|
1881
|
+
// Pass 2: Multiple offset washes with radial displacement
|
|
1882
|
+
ctx.globalAlpha = savedAlpha * (0.25 / passes * 2);
|
|
1473
1883
|
for(let p = 0; p < passes; p++){
|
|
1474
|
-
|
|
1475
|
-
const
|
|
1884
|
+
// Radial outward displacement (not uniform) for organic bleed
|
|
1885
|
+
const angle = rng ? rng() * Math.PI * 2 : p * Math.PI / 2;
|
|
1886
|
+
const dist = rng ? rng() * size * 0.05 : size * 0.02;
|
|
1887
|
+
const jx = Math.cos(angle) * dist;
|
|
1888
|
+
const jy = Math.sin(angle) * dist;
|
|
1476
1889
|
ctx.save();
|
|
1477
1890
|
ctx.translate(jx, jy);
|
|
1478
1891
|
ctx.fill();
|
|
1479
1892
|
ctx.restore();
|
|
1480
1893
|
}
|
|
1894
|
+
// Pass 3: Edge darkening — draw a slightly smaller shape with lighter fill
|
|
1895
|
+
// to simulate pigment pooling at boundaries
|
|
1896
|
+
ctx.globalAlpha = savedAlpha * 0.35;
|
|
1897
|
+
ctx.save();
|
|
1898
|
+
const innerScale = 0.85 + (rng ? rng() * 0.08 : 0);
|
|
1899
|
+
ctx.scale(innerScale, innerScale);
|
|
1900
|
+
// Lighten the fill for the inner area
|
|
1901
|
+
const origFill = ctx.fillStyle;
|
|
1902
|
+
if (typeof fillColor === "string") ctx.fillStyle = fillColor.replace(/[\d.]+\)$/, (m)=>{
|
|
1903
|
+
const v = parseFloat(m);
|
|
1904
|
+
return Math.min(1, v * 1.4).toFixed(2) + ")";
|
|
1905
|
+
});
|
|
1906
|
+
ctx.fill();
|
|
1907
|
+
ctx.fillStyle = origFill;
|
|
1908
|
+
ctx.restore();
|
|
1481
1909
|
ctx.globalAlpha = savedAlpha;
|
|
1482
|
-
//
|
|
1483
|
-
ctx.globalAlpha *= 0.
|
|
1910
|
+
// Soft stroke on top — thinner than normal for delicacy
|
|
1911
|
+
ctx.globalAlpha *= 0.25;
|
|
1912
|
+
ctx.lineWidth = strokeWidth * 0.6;
|
|
1484
1913
|
ctx.stroke();
|
|
1485
|
-
ctx.globalAlpha /= 0.
|
|
1914
|
+
ctx.globalAlpha /= 0.25;
|
|
1486
1915
|
break;
|
|
1487
1916
|
}
|
|
1488
1917
|
case "hatched":
|
|
@@ -1555,6 +1984,213 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1555
1984
|
ctx.lineDashOffset = 0;
|
|
1556
1985
|
break;
|
|
1557
1986
|
}
|
|
1987
|
+
case "stipple":
|
|
1988
|
+
{
|
|
1989
|
+
// Dot-fill texture — clip to shape, then scatter dots
|
|
1990
|
+
const savedAlphaS = ctx.globalAlpha;
|
|
1991
|
+
ctx.globalAlpha = savedAlphaS * 0.15;
|
|
1992
|
+
ctx.fill(); // ghost fill
|
|
1993
|
+
ctx.globalAlpha = savedAlphaS;
|
|
1994
|
+
ctx.save();
|
|
1995
|
+
ctx.clip();
|
|
1996
|
+
const dotSpacing = Math.max(2, size * 0.03);
|
|
1997
|
+
const extent = size * 0.55;
|
|
1998
|
+
ctx.globalAlpha = savedAlphaS * 0.7;
|
|
1999
|
+
for(let dx = -extent; dx <= extent; dx += dotSpacing)for(let dy = -extent; dy <= extent; dy += dotSpacing){
|
|
2000
|
+
// Jitter each dot position for organic feel
|
|
2001
|
+
const jx = rng ? (rng() - 0.5) * dotSpacing * 0.6 : 0;
|
|
2002
|
+
const jy = rng ? (rng() - 0.5) * dotSpacing * 0.6 : 0;
|
|
2003
|
+
const dotR = rng ? dotSpacing * (0.15 + rng() * 0.2) : dotSpacing * 0.2;
|
|
2004
|
+
ctx.beginPath();
|
|
2005
|
+
ctx.arc(dx + jx, dy + jy, dotR, 0, Math.PI * 2);
|
|
2006
|
+
ctx.fill();
|
|
2007
|
+
}
|
|
2008
|
+
ctx.restore();
|
|
2009
|
+
ctx.globalAlpha = savedAlphaS;
|
|
2010
|
+
// Outline
|
|
2011
|
+
ctx.globalAlpha *= 0.4;
|
|
2012
|
+
ctx.stroke();
|
|
2013
|
+
ctx.globalAlpha /= 0.4;
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
case "stencil":
|
|
2017
|
+
{
|
|
2018
|
+
// Negative-space cutout — fill a rectangle, then erase the shape
|
|
2019
|
+
const savedAlphaSt = ctx.globalAlpha;
|
|
2020
|
+
// Fill a bounding area with the stroke color
|
|
2021
|
+
ctx.globalAlpha = savedAlphaSt * 0.5;
|
|
2022
|
+
ctx.fillStyle = strokeColor;
|
|
2023
|
+
ctx.fillRect(-size * 0.6, -size * 0.6, size * 1.2, size * 1.2);
|
|
2024
|
+
// Cut out the shape using destination-out
|
|
2025
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2026
|
+
ctx.globalAlpha = 1;
|
|
2027
|
+
ctx.fill();
|
|
2028
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2029
|
+
ctx.globalAlpha = savedAlphaSt;
|
|
2030
|
+
// Subtle outline of the cutout
|
|
2031
|
+
ctx.globalAlpha *= 0.3;
|
|
2032
|
+
ctx.stroke();
|
|
2033
|
+
ctx.globalAlpha /= 0.3;
|
|
2034
|
+
break;
|
|
2035
|
+
}
|
|
2036
|
+
case "noise-grain":
|
|
2037
|
+
{
|
|
2038
|
+
// Procedural noise grain texture clipped to shape boundary
|
|
2039
|
+
const savedAlphaN = ctx.globalAlpha;
|
|
2040
|
+
ctx.globalAlpha = savedAlphaN * 0.25;
|
|
2041
|
+
ctx.fill(); // base tint
|
|
2042
|
+
ctx.globalAlpha = savedAlphaN;
|
|
2043
|
+
ctx.save();
|
|
2044
|
+
ctx.clip();
|
|
2045
|
+
const grainSpacing = Math.max(1.5, size * 0.015);
|
|
2046
|
+
const extentN = size * 0.55;
|
|
2047
|
+
ctx.globalAlpha = savedAlphaN * 0.6;
|
|
2048
|
+
for(let gx = -extentN; gx <= extentN; gx += grainSpacing)for(let gy = -extentN; gy <= extentN; gy += grainSpacing){
|
|
2049
|
+
if (!rng) break;
|
|
2050
|
+
const jx = (rng() - 0.5) * grainSpacing * 1.2;
|
|
2051
|
+
const jy = (rng() - 0.5) * grainSpacing * 1.2;
|
|
2052
|
+
const brightness = rng() > 0.5 ? 255 : 0;
|
|
2053
|
+
const dotAlpha = 0.15 + rng() * 0.35;
|
|
2054
|
+
ctx.globalAlpha = savedAlphaN * dotAlpha;
|
|
2055
|
+
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
2056
|
+
const dotSize = grainSpacing * (0.3 + rng() * 0.5);
|
|
2057
|
+
ctx.fillRect(gx + jx, gy + jy, dotSize, dotSize);
|
|
2058
|
+
}
|
|
2059
|
+
ctx.restore();
|
|
2060
|
+
ctx.fillStyle = fillColor;
|
|
2061
|
+
ctx.globalAlpha = savedAlphaN;
|
|
2062
|
+
ctx.globalAlpha *= 0.4;
|
|
2063
|
+
ctx.stroke();
|
|
2064
|
+
ctx.globalAlpha /= 0.4;
|
|
2065
|
+
break;
|
|
2066
|
+
}
|
|
2067
|
+
case "wood-grain":
|
|
2068
|
+
{
|
|
2069
|
+
// Parallel wavy lines simulating wood grain, clipped to shape
|
|
2070
|
+
const savedAlphaW = ctx.globalAlpha;
|
|
2071
|
+
ctx.globalAlpha = savedAlphaW * 0.2;
|
|
2072
|
+
ctx.fill(); // base tint
|
|
2073
|
+
ctx.globalAlpha = savedAlphaW;
|
|
2074
|
+
ctx.save();
|
|
2075
|
+
ctx.clip();
|
|
2076
|
+
const grainLineSpacing = Math.max(2, size * 0.035);
|
|
2077
|
+
const extentW = size * 0.55;
|
|
2078
|
+
const waveFreq = rng ? 3 + rng() * 5 : 5;
|
|
2079
|
+
const waveAmp = rng ? size * (0.01 + rng() * 0.03) : size * 0.02;
|
|
2080
|
+
const grainAngle = rng ? rng() * Math.PI : Math.PI * 0.25;
|
|
2081
|
+
ctx.lineWidth = Math.max(0.5, strokeWidth * 0.3);
|
|
2082
|
+
ctx.globalAlpha = savedAlphaW * 0.5;
|
|
2083
|
+
const cosG = Math.cos(grainAngle);
|
|
2084
|
+
const sinG = Math.sin(grainAngle);
|
|
2085
|
+
for(let d = -extentW; d <= extentW; d += grainLineSpacing){
|
|
2086
|
+
ctx.beginPath();
|
|
2087
|
+
for(let t = -extentW; t <= extentW; t += 2){
|
|
2088
|
+
const wave = Math.sin(t / extentW * waveFreq * Math.PI) * waveAmp;
|
|
2089
|
+
const px = t * cosG - (d + wave) * sinG;
|
|
2090
|
+
const py = t * sinG + (d + wave) * cosG;
|
|
2091
|
+
if (t === -extentW) ctx.moveTo(px, py);
|
|
2092
|
+
else ctx.lineTo(px, py);
|
|
2093
|
+
}
|
|
2094
|
+
ctx.stroke();
|
|
2095
|
+
}
|
|
2096
|
+
ctx.restore();
|
|
2097
|
+
ctx.globalAlpha = savedAlphaW;
|
|
2098
|
+
ctx.globalAlpha *= 0.35;
|
|
2099
|
+
ctx.stroke();
|
|
2100
|
+
ctx.globalAlpha /= 0.35;
|
|
2101
|
+
break;
|
|
2102
|
+
}
|
|
2103
|
+
case "marble-vein":
|
|
2104
|
+
{
|
|
2105
|
+
// Branching vein lines on a soft fill, clipped to shape
|
|
2106
|
+
const savedAlphaM = ctx.globalAlpha;
|
|
2107
|
+
ctx.globalAlpha = savedAlphaM * 0.35;
|
|
2108
|
+
ctx.fill(); // soft base
|
|
2109
|
+
ctx.globalAlpha = savedAlphaM;
|
|
2110
|
+
ctx.save();
|
|
2111
|
+
ctx.clip();
|
|
2112
|
+
const veinCount = rng ? 2 + Math.floor(rng() * 3) : 3;
|
|
2113
|
+
const extentM = size * 0.45;
|
|
2114
|
+
ctx.lineWidth = Math.max(0.5, strokeWidth * 0.5);
|
|
2115
|
+
ctx.globalAlpha = savedAlphaM * 0.4;
|
|
2116
|
+
for(let v = 0; v < veinCount; v++){
|
|
2117
|
+
const startX = rng ? (rng() - 0.5) * extentM * 2 : 0;
|
|
2118
|
+
const startY = rng ? -extentM + rng() * extentM * 0.5 : -extentM;
|
|
2119
|
+
let vx = startX;
|
|
2120
|
+
let vy = startY;
|
|
2121
|
+
const steps = 15 + (rng ? Math.floor(rng() * 15) : 10);
|
|
2122
|
+
const stepLen = size * 0.04;
|
|
2123
|
+
ctx.beginPath();
|
|
2124
|
+
ctx.moveTo(vx, vy);
|
|
2125
|
+
for(let s = 0; s < steps; s++){
|
|
2126
|
+
const drift = rng ? (rng() - 0.5) * stepLen * 1.5 : 0;
|
|
2127
|
+
vx += drift;
|
|
2128
|
+
vy += stepLen;
|
|
2129
|
+
ctx.lineTo(vx, vy);
|
|
2130
|
+
// Branch ~20% of the time
|
|
2131
|
+
if (rng && rng() < 0.2 && s > 2 && s < steps - 3) {
|
|
2132
|
+
const branchDir = rng() < 0.5 ? -1 : 1;
|
|
2133
|
+
let bx = vx;
|
|
2134
|
+
let by = vy;
|
|
2135
|
+
const bSteps = 3 + Math.floor(rng() * 5);
|
|
2136
|
+
ctx.moveTo(bx, by);
|
|
2137
|
+
for(let bs = 0; bs < bSteps; bs++){
|
|
2138
|
+
bx += branchDir * stepLen * (0.5 + rng() * 0.5);
|
|
2139
|
+
by += stepLen * 0.6;
|
|
2140
|
+
ctx.lineTo(bx, by);
|
|
2141
|
+
}
|
|
2142
|
+
ctx.moveTo(vx, vy); // return to main vein
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
ctx.stroke();
|
|
2146
|
+
}
|
|
2147
|
+
ctx.restore();
|
|
2148
|
+
ctx.globalAlpha = savedAlphaM;
|
|
2149
|
+
ctx.globalAlpha *= 0.3;
|
|
2150
|
+
ctx.stroke();
|
|
2151
|
+
ctx.globalAlpha /= 0.3;
|
|
2152
|
+
break;
|
|
2153
|
+
}
|
|
2154
|
+
case "fabric-weave":
|
|
2155
|
+
{
|
|
2156
|
+
// Interlocking horizontal/vertical threads clipped to shape
|
|
2157
|
+
const savedAlphaF = ctx.globalAlpha;
|
|
2158
|
+
ctx.globalAlpha = savedAlphaF * 0.15;
|
|
2159
|
+
ctx.fill(); // ghost base
|
|
2160
|
+
ctx.globalAlpha = savedAlphaF;
|
|
2161
|
+
ctx.save();
|
|
2162
|
+
ctx.clip();
|
|
2163
|
+
const threadSpacing = Math.max(2, size * 0.04);
|
|
2164
|
+
const extentF = size * 0.55;
|
|
2165
|
+
ctx.lineWidth = Math.max(0.8, threadSpacing * 0.5);
|
|
2166
|
+
ctx.globalAlpha = savedAlphaF * 0.55;
|
|
2167
|
+
// Horizontal threads
|
|
2168
|
+
for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
|
|
2169
|
+
ctx.beginPath();
|
|
2170
|
+
ctx.moveTo(-extentF, y);
|
|
2171
|
+
ctx.lineTo(extentF, y);
|
|
2172
|
+
ctx.stroke();
|
|
2173
|
+
}
|
|
2174
|
+
// Vertical threads (offset by half spacing for weave effect)
|
|
2175
|
+
ctx.globalAlpha = savedAlphaF * 0.45;
|
|
2176
|
+
ctx.strokeStyle = fillColor;
|
|
2177
|
+
for(let x = -extentF; x <= extentF; x += threadSpacing * 2){
|
|
2178
|
+
ctx.beginPath();
|
|
2179
|
+
for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
|
|
2180
|
+
// Over-under: draw segment, skip segment
|
|
2181
|
+
ctx.moveTo(x, y);
|
|
2182
|
+
ctx.lineTo(x, y + threadSpacing);
|
|
2183
|
+
}
|
|
2184
|
+
ctx.stroke();
|
|
2185
|
+
}
|
|
2186
|
+
ctx.strokeStyle = strokeColor;
|
|
2187
|
+
ctx.restore();
|
|
2188
|
+
ctx.globalAlpha = savedAlphaF;
|
|
2189
|
+
ctx.globalAlpha *= 0.3;
|
|
2190
|
+
ctx.stroke();
|
|
2191
|
+
ctx.globalAlpha /= 0.3;
|
|
2192
|
+
break;
|
|
2193
|
+
}
|
|
1558
2194
|
case "fill-and-stroke":
|
|
1559
2195
|
default:
|
|
1560
2196
|
ctx.fill();
|
|
@@ -1601,77 +2237,1076 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1601
2237
|
});
|
|
1602
2238
|
ctx.restore();
|
|
1603
2239
|
}
|
|
2240
|
+
function $9beb8f41637c29fd$export$8bd8bbd1a8e53689(ctx, shape, x, y, config) {
|
|
2241
|
+
const { mirrorAxis: mirrorAxis = "horizontal", mirrorGap: mirrorGap = 0 } = config;
|
|
2242
|
+
// Draw the primary shape
|
|
2243
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config);
|
|
2244
|
+
// Draw the mirrored copy
|
|
2245
|
+
ctx.save();
|
|
2246
|
+
const savedAlpha = ctx.globalAlpha;
|
|
2247
|
+
ctx.globalAlpha = savedAlpha * 0.7; // mirror is slightly softer
|
|
2248
|
+
switch(mirrorAxis){
|
|
2249
|
+
case "horizontal":
|
|
2250
|
+
// Reflect across vertical axis at shape position
|
|
2251
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y + mirrorGap, {
|
|
2252
|
+
...config,
|
|
2253
|
+
rotation: -(config.rotation || 0),
|
|
2254
|
+
size: config.size * 0.95
|
|
2255
|
+
});
|
|
2256
|
+
break;
|
|
2257
|
+
case "vertical":
|
|
2258
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x + mirrorGap, y, {
|
|
2259
|
+
...config,
|
|
2260
|
+
rotation: 180 - (config.rotation || 0),
|
|
2261
|
+
size: config.size * 0.95
|
|
2262
|
+
});
|
|
2263
|
+
break;
|
|
2264
|
+
case "diagonal":
|
|
2265
|
+
// Reflect across 45° axis
|
|
2266
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x + mirrorGap * 0.7, y + mirrorGap * 0.7, {
|
|
2267
|
+
...config,
|
|
2268
|
+
rotation: 90 - (config.rotation || 0),
|
|
2269
|
+
size: config.size * 0.9
|
|
2270
|
+
});
|
|
2271
|
+
break;
|
|
2272
|
+
case "radial-4":
|
|
2273
|
+
// Four-way radial mirror
|
|
2274
|
+
for(let i = 1; i < 4; i++){
|
|
2275
|
+
const angle = i / 4 * Math.PI * 2;
|
|
2276
|
+
const mx = x + Math.cos(angle) * mirrorGap;
|
|
2277
|
+
const my = y + Math.sin(angle) * mirrorGap;
|
|
2278
|
+
ctx.globalAlpha = savedAlpha * (0.7 - i * 0.1);
|
|
2279
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, mx, my, {
|
|
2280
|
+
...config,
|
|
2281
|
+
rotation: (config.rotation || 0) + i * 90,
|
|
2282
|
+
size: config.size * (0.95 - i * 0.05)
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
break;
|
|
2286
|
+
}
|
|
2287
|
+
ctx.globalAlpha = savedAlpha;
|
|
2288
|
+
ctx.restore();
|
|
2289
|
+
}
|
|
2290
|
+
function $9beb8f41637c29fd$export$879206e23912d1a9(rng) {
|
|
2291
|
+
const roll = rng();
|
|
2292
|
+
if (roll < 0.60) return null;
|
|
2293
|
+
if (roll < 0.75) return "horizontal";
|
|
2294
|
+
if (roll < 0.87) return "vertical";
|
|
2295
|
+
if (roll < 0.95) return "diagonal";
|
|
2296
|
+
return "radial-4";
|
|
2297
|
+
}
|
|
1604
2298
|
|
|
1605
2299
|
|
|
1606
2300
|
|
|
1607
|
-
|
|
1608
|
-
/**
|
|
1609
|
-
* Configuration options for image generation.
|
|
1610
|
-
*/ const $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f = {
|
|
1611
|
-
width: 2048,
|
|
1612
|
-
height: 2048,
|
|
1613
|
-
gridSize: 5,
|
|
1614
|
-
layers: 4,
|
|
1615
|
-
minShapeSize: 30,
|
|
1616
|
-
maxShapeSize: 400,
|
|
1617
|
-
baseOpacity: 0.7,
|
|
1618
|
-
opacityReduction: 0.12,
|
|
1619
|
-
shapesPerLayer: 0
|
|
1620
|
-
};
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
2301
|
/**
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
2302
|
+
* Shape affinity system — controls which shapes look good together,
|
|
2303
|
+
* quality tiers for different rendering contexts, and size preferences.
|
|
1626
2304
|
*
|
|
1627
|
-
*
|
|
1628
|
-
* that
|
|
1629
|
-
*/ // ──
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
"
|
|
1643
|
-
"
|
|
1644
|
-
"
|
|
2305
|
+
* This replaces the naive "pick any shape" approach with intentional
|
|
2306
|
+
* curation that produces more cohesive compositions.
|
|
2307
|
+
*/ // ── Quality tiers ───────────────────────────────────────────────────
|
|
2308
|
+
// Not all shapes render equally well at all sizes or in all contexts.
|
|
2309
|
+
// Tier 1 shapes are visually strong at any size; Tier 3 shapes need
|
|
2310
|
+
// specific conditions to look good.
|
|
2311
|
+
const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
2312
|
+
// ── Basic shapes ──────────────────────────────────────────────
|
|
2313
|
+
circle: {
|
|
2314
|
+
tier: 1,
|
|
2315
|
+
minSizeFraction: 0.05,
|
|
2316
|
+
maxSizeFraction: 1.0,
|
|
2317
|
+
affinities: [
|
|
2318
|
+
"circle",
|
|
2319
|
+
"blob",
|
|
2320
|
+
"hexagon",
|
|
2321
|
+
"flowerOfLife",
|
|
2322
|
+
"seedOfLife"
|
|
1645
2323
|
],
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
2324
|
+
category: "basic",
|
|
2325
|
+
heroCandidate: false,
|
|
2326
|
+
bestStyles: [
|
|
2327
|
+
"fill-only",
|
|
2328
|
+
"watercolor",
|
|
2329
|
+
"fill-and-stroke"
|
|
2330
|
+
]
|
|
1651
2331
|
},
|
|
1652
|
-
{
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
2332
|
+
square: {
|
|
2333
|
+
tier: 2,
|
|
2334
|
+
minSizeFraction: 0.08,
|
|
2335
|
+
maxSizeFraction: 0.7,
|
|
2336
|
+
affinities: [
|
|
2337
|
+
"square",
|
|
2338
|
+
"diamond",
|
|
2339
|
+
"superellipse",
|
|
2340
|
+
"islamicPattern"
|
|
2341
|
+
],
|
|
2342
|
+
category: "basic",
|
|
2343
|
+
heroCandidate: false,
|
|
2344
|
+
bestStyles: [
|
|
1663
2345
|
"fill-and-stroke",
|
|
1664
2346
|
"stroke-only",
|
|
1665
|
-
"
|
|
2347
|
+
"hatched"
|
|
2348
|
+
]
|
|
2349
|
+
},
|
|
2350
|
+
triangle: {
|
|
2351
|
+
tier: 1,
|
|
2352
|
+
minSizeFraction: 0.06,
|
|
2353
|
+
maxSizeFraction: 0.9,
|
|
2354
|
+
affinities: [
|
|
2355
|
+
"triangle",
|
|
2356
|
+
"diamond",
|
|
2357
|
+
"hexagon",
|
|
2358
|
+
"merkaba",
|
|
2359
|
+
"sriYantra"
|
|
1666
2360
|
],
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
2361
|
+
category: "basic",
|
|
2362
|
+
heroCandidate: false,
|
|
2363
|
+
bestStyles: [
|
|
2364
|
+
"fill-and-stroke",
|
|
2365
|
+
"fill-only",
|
|
2366
|
+
"watercolor"
|
|
2367
|
+
]
|
|
1672
2368
|
},
|
|
1673
|
-
{
|
|
1674
|
-
|
|
2369
|
+
hexagon: {
|
|
2370
|
+
tier: 1,
|
|
2371
|
+
minSizeFraction: 0.05,
|
|
2372
|
+
maxSizeFraction: 1.0,
|
|
2373
|
+
affinities: [
|
|
2374
|
+
"hexagon",
|
|
2375
|
+
"circle",
|
|
2376
|
+
"flowerOfLife",
|
|
2377
|
+
"metatronsCube",
|
|
2378
|
+
"triangle"
|
|
2379
|
+
],
|
|
2380
|
+
category: "basic",
|
|
2381
|
+
heroCandidate: false,
|
|
2382
|
+
bestStyles: [
|
|
2383
|
+
"fill-only",
|
|
2384
|
+
"fill-and-stroke",
|
|
2385
|
+
"watercolor"
|
|
2386
|
+
]
|
|
2387
|
+
},
|
|
2388
|
+
star: {
|
|
2389
|
+
tier: 2,
|
|
2390
|
+
minSizeFraction: 0.08,
|
|
2391
|
+
maxSizeFraction: 0.6,
|
|
2392
|
+
affinities: [
|
|
2393
|
+
"star",
|
|
2394
|
+
"circle",
|
|
2395
|
+
"mandala",
|
|
2396
|
+
"spirograph"
|
|
2397
|
+
],
|
|
2398
|
+
category: "basic",
|
|
2399
|
+
heroCandidate: false,
|
|
2400
|
+
bestStyles: [
|
|
2401
|
+
"fill-and-stroke",
|
|
2402
|
+
"stroke-only",
|
|
2403
|
+
"dashed"
|
|
2404
|
+
]
|
|
2405
|
+
},
|
|
2406
|
+
"jacked-star": {
|
|
2407
|
+
tier: 3,
|
|
2408
|
+
minSizeFraction: 0.1,
|
|
2409
|
+
maxSizeFraction: 0.4,
|
|
2410
|
+
affinities: [
|
|
2411
|
+
"star",
|
|
2412
|
+
"circle"
|
|
2413
|
+
],
|
|
2414
|
+
category: "basic",
|
|
2415
|
+
heroCandidate: false,
|
|
2416
|
+
bestStyles: [
|
|
2417
|
+
"stroke-only",
|
|
2418
|
+
"dashed"
|
|
2419
|
+
]
|
|
2420
|
+
},
|
|
2421
|
+
heart: {
|
|
2422
|
+
tier: 3,
|
|
2423
|
+
minSizeFraction: 0.1,
|
|
2424
|
+
maxSizeFraction: 0.5,
|
|
2425
|
+
affinities: [
|
|
2426
|
+
"circle",
|
|
2427
|
+
"blob"
|
|
2428
|
+
],
|
|
2429
|
+
category: "basic",
|
|
2430
|
+
heroCandidate: false,
|
|
2431
|
+
bestStyles: [
|
|
2432
|
+
"fill-only",
|
|
2433
|
+
"watercolor"
|
|
2434
|
+
]
|
|
2435
|
+
},
|
|
2436
|
+
diamond: {
|
|
2437
|
+
tier: 2,
|
|
2438
|
+
minSizeFraction: 0.06,
|
|
2439
|
+
maxSizeFraction: 0.8,
|
|
2440
|
+
affinities: [
|
|
2441
|
+
"diamond",
|
|
2442
|
+
"triangle",
|
|
2443
|
+
"square",
|
|
2444
|
+
"merkaba"
|
|
2445
|
+
],
|
|
2446
|
+
category: "basic",
|
|
2447
|
+
heroCandidate: false,
|
|
2448
|
+
bestStyles: [
|
|
2449
|
+
"fill-and-stroke",
|
|
2450
|
+
"fill-only",
|
|
2451
|
+
"double-stroke"
|
|
2452
|
+
]
|
|
2453
|
+
},
|
|
2454
|
+
cube: {
|
|
2455
|
+
tier: 3,
|
|
2456
|
+
minSizeFraction: 0.08,
|
|
2457
|
+
maxSizeFraction: 0.5,
|
|
2458
|
+
affinities: [
|
|
2459
|
+
"square",
|
|
2460
|
+
"diamond"
|
|
2461
|
+
],
|
|
2462
|
+
category: "basic",
|
|
2463
|
+
heroCandidate: false,
|
|
2464
|
+
bestStyles: [
|
|
2465
|
+
"stroke-only",
|
|
2466
|
+
"fill-and-stroke"
|
|
2467
|
+
]
|
|
2468
|
+
},
|
|
2469
|
+
// ── Complex shapes ────────────────────────────────────────────
|
|
2470
|
+
platonicSolid: {
|
|
2471
|
+
tier: 2,
|
|
2472
|
+
minSizeFraction: 0.15,
|
|
2473
|
+
maxSizeFraction: 0.8,
|
|
2474
|
+
affinities: [
|
|
2475
|
+
"metatronsCube",
|
|
2476
|
+
"merkaba",
|
|
2477
|
+
"hexagon",
|
|
2478
|
+
"triangle"
|
|
2479
|
+
],
|
|
2480
|
+
category: "complex",
|
|
2481
|
+
heroCandidate: true,
|
|
2482
|
+
bestStyles: [
|
|
2483
|
+
"stroke-only",
|
|
2484
|
+
"double-stroke",
|
|
2485
|
+
"dashed"
|
|
2486
|
+
]
|
|
2487
|
+
},
|
|
2488
|
+
fibonacciSpiral: {
|
|
2489
|
+
tier: 1,
|
|
2490
|
+
minSizeFraction: 0.2,
|
|
2491
|
+
maxSizeFraction: 1.0,
|
|
2492
|
+
affinities: [
|
|
2493
|
+
"circle",
|
|
2494
|
+
"rose",
|
|
2495
|
+
"spirograph",
|
|
2496
|
+
"flowerOfLife"
|
|
2497
|
+
],
|
|
2498
|
+
category: "complex",
|
|
2499
|
+
heroCandidate: true,
|
|
2500
|
+
bestStyles: [
|
|
2501
|
+
"stroke-only",
|
|
2502
|
+
"incomplete",
|
|
2503
|
+
"watercolor"
|
|
2504
|
+
]
|
|
2505
|
+
},
|
|
2506
|
+
islamicPattern: {
|
|
2507
|
+
tier: 2,
|
|
2508
|
+
minSizeFraction: 0.25,
|
|
2509
|
+
maxSizeFraction: 0.9,
|
|
2510
|
+
affinities: [
|
|
2511
|
+
"square",
|
|
2512
|
+
"hexagon",
|
|
2513
|
+
"star",
|
|
2514
|
+
"mandala"
|
|
2515
|
+
],
|
|
2516
|
+
category: "complex",
|
|
2517
|
+
heroCandidate: true,
|
|
2518
|
+
bestStyles: [
|
|
2519
|
+
"stroke-only",
|
|
2520
|
+
"dashed",
|
|
2521
|
+
"hatched"
|
|
2522
|
+
]
|
|
2523
|
+
},
|
|
2524
|
+
celticKnot: {
|
|
2525
|
+
tier: 2,
|
|
2526
|
+
minSizeFraction: 0.2,
|
|
2527
|
+
maxSizeFraction: 0.7,
|
|
2528
|
+
affinities: [
|
|
2529
|
+
"circle",
|
|
2530
|
+
"lissajous",
|
|
2531
|
+
"spirograph"
|
|
2532
|
+
],
|
|
2533
|
+
category: "complex",
|
|
2534
|
+
heroCandidate: true,
|
|
2535
|
+
bestStyles: [
|
|
2536
|
+
"stroke-only",
|
|
2537
|
+
"double-stroke"
|
|
2538
|
+
]
|
|
2539
|
+
},
|
|
2540
|
+
merkaba: {
|
|
2541
|
+
tier: 1,
|
|
2542
|
+
minSizeFraction: 0.15,
|
|
2543
|
+
maxSizeFraction: 1.0,
|
|
2544
|
+
affinities: [
|
|
2545
|
+
"triangle",
|
|
2546
|
+
"diamond",
|
|
2547
|
+
"sriYantra",
|
|
2548
|
+
"metatronsCube"
|
|
2549
|
+
],
|
|
2550
|
+
category: "complex",
|
|
2551
|
+
heroCandidate: true,
|
|
2552
|
+
bestStyles: [
|
|
2553
|
+
"stroke-only",
|
|
2554
|
+
"fill-and-stroke",
|
|
2555
|
+
"double-stroke"
|
|
2556
|
+
]
|
|
2557
|
+
},
|
|
2558
|
+
mandala: {
|
|
2559
|
+
tier: 1,
|
|
2560
|
+
minSizeFraction: 0.2,
|
|
2561
|
+
maxSizeFraction: 1.0,
|
|
2562
|
+
affinities: [
|
|
2563
|
+
"circle",
|
|
2564
|
+
"flowerOfLife",
|
|
2565
|
+
"spirograph",
|
|
2566
|
+
"rose"
|
|
2567
|
+
],
|
|
2568
|
+
category: "complex",
|
|
2569
|
+
heroCandidate: true,
|
|
2570
|
+
bestStyles: [
|
|
2571
|
+
"stroke-only",
|
|
2572
|
+
"dashed",
|
|
2573
|
+
"incomplete"
|
|
2574
|
+
]
|
|
2575
|
+
},
|
|
2576
|
+
fractal: {
|
|
2577
|
+
tier: 2,
|
|
2578
|
+
minSizeFraction: 0.2,
|
|
2579
|
+
maxSizeFraction: 0.8,
|
|
2580
|
+
affinities: [
|
|
2581
|
+
"blob",
|
|
2582
|
+
"lissajous",
|
|
2583
|
+
"circle"
|
|
2584
|
+
],
|
|
2585
|
+
category: "complex",
|
|
2586
|
+
heroCandidate: true,
|
|
2587
|
+
bestStyles: [
|
|
2588
|
+
"stroke-only",
|
|
2589
|
+
"incomplete"
|
|
2590
|
+
]
|
|
2591
|
+
},
|
|
2592
|
+
// ── Sacred shapes ─────────────────────────────────────────────
|
|
2593
|
+
flowerOfLife: {
|
|
2594
|
+
tier: 1,
|
|
2595
|
+
minSizeFraction: 0.2,
|
|
2596
|
+
maxSizeFraction: 1.0,
|
|
2597
|
+
affinities: [
|
|
2598
|
+
"circle",
|
|
2599
|
+
"hexagon",
|
|
2600
|
+
"seedOfLife",
|
|
2601
|
+
"eggOfLife",
|
|
2602
|
+
"metatronsCube"
|
|
2603
|
+
],
|
|
2604
|
+
category: "sacred",
|
|
2605
|
+
heroCandidate: true,
|
|
2606
|
+
bestStyles: [
|
|
2607
|
+
"stroke-only",
|
|
2608
|
+
"watercolor",
|
|
2609
|
+
"incomplete"
|
|
2610
|
+
]
|
|
2611
|
+
},
|
|
2612
|
+
treeOfLife: {
|
|
2613
|
+
tier: 2,
|
|
2614
|
+
minSizeFraction: 0.25,
|
|
2615
|
+
maxSizeFraction: 0.9,
|
|
2616
|
+
affinities: [
|
|
2617
|
+
"circle",
|
|
2618
|
+
"flowerOfLife",
|
|
2619
|
+
"metatronsCube"
|
|
2620
|
+
],
|
|
2621
|
+
category: "sacred",
|
|
2622
|
+
heroCandidate: true,
|
|
2623
|
+
bestStyles: [
|
|
2624
|
+
"stroke-only",
|
|
2625
|
+
"double-stroke"
|
|
2626
|
+
]
|
|
2627
|
+
},
|
|
2628
|
+
metatronsCube: {
|
|
2629
|
+
tier: 1,
|
|
2630
|
+
minSizeFraction: 0.2,
|
|
2631
|
+
maxSizeFraction: 1.0,
|
|
2632
|
+
affinities: [
|
|
2633
|
+
"hexagon",
|
|
2634
|
+
"flowerOfLife",
|
|
2635
|
+
"platonicSolid",
|
|
2636
|
+
"merkaba"
|
|
2637
|
+
],
|
|
2638
|
+
category: "sacred",
|
|
2639
|
+
heroCandidate: true,
|
|
2640
|
+
bestStyles: [
|
|
2641
|
+
"stroke-only",
|
|
2642
|
+
"dashed",
|
|
2643
|
+
"incomplete"
|
|
2644
|
+
]
|
|
2645
|
+
},
|
|
2646
|
+
sriYantra: {
|
|
2647
|
+
tier: 1,
|
|
2648
|
+
minSizeFraction: 0.2,
|
|
2649
|
+
maxSizeFraction: 1.0,
|
|
2650
|
+
affinities: [
|
|
2651
|
+
"triangle",
|
|
2652
|
+
"merkaba",
|
|
2653
|
+
"mandala",
|
|
2654
|
+
"diamond"
|
|
2655
|
+
],
|
|
2656
|
+
category: "sacred",
|
|
2657
|
+
heroCandidate: true,
|
|
2658
|
+
bestStyles: [
|
|
2659
|
+
"stroke-only",
|
|
2660
|
+
"fill-and-stroke",
|
|
2661
|
+
"double-stroke"
|
|
2662
|
+
]
|
|
2663
|
+
},
|
|
2664
|
+
seedOfLife: {
|
|
2665
|
+
tier: 1,
|
|
2666
|
+
minSizeFraction: 0.15,
|
|
2667
|
+
maxSizeFraction: 0.9,
|
|
2668
|
+
affinities: [
|
|
2669
|
+
"circle",
|
|
2670
|
+
"flowerOfLife",
|
|
2671
|
+
"eggOfLife",
|
|
2672
|
+
"hexagon"
|
|
2673
|
+
],
|
|
2674
|
+
category: "sacred",
|
|
2675
|
+
heroCandidate: true,
|
|
2676
|
+
bestStyles: [
|
|
2677
|
+
"stroke-only",
|
|
2678
|
+
"watercolor",
|
|
2679
|
+
"fill-only"
|
|
2680
|
+
]
|
|
2681
|
+
},
|
|
2682
|
+
vesicaPiscis: {
|
|
2683
|
+
tier: 2,
|
|
2684
|
+
minSizeFraction: 0.15,
|
|
2685
|
+
maxSizeFraction: 0.7,
|
|
2686
|
+
affinities: [
|
|
2687
|
+
"circle",
|
|
2688
|
+
"seedOfLife",
|
|
2689
|
+
"flowerOfLife"
|
|
2690
|
+
],
|
|
2691
|
+
category: "sacred",
|
|
2692
|
+
heroCandidate: false,
|
|
2693
|
+
bestStyles: [
|
|
2694
|
+
"stroke-only",
|
|
2695
|
+
"watercolor"
|
|
2696
|
+
]
|
|
2697
|
+
},
|
|
2698
|
+
torus: {
|
|
2699
|
+
tier: 3,
|
|
2700
|
+
minSizeFraction: 0.2,
|
|
2701
|
+
maxSizeFraction: 0.6,
|
|
2702
|
+
affinities: [
|
|
2703
|
+
"circle",
|
|
2704
|
+
"spirograph",
|
|
2705
|
+
"waveRing"
|
|
2706
|
+
],
|
|
2707
|
+
category: "sacred",
|
|
2708
|
+
heroCandidate: false,
|
|
2709
|
+
bestStyles: [
|
|
2710
|
+
"stroke-only",
|
|
2711
|
+
"dashed"
|
|
2712
|
+
]
|
|
2713
|
+
},
|
|
2714
|
+
eggOfLife: {
|
|
2715
|
+
tier: 2,
|
|
2716
|
+
minSizeFraction: 0.15,
|
|
2717
|
+
maxSizeFraction: 0.8,
|
|
2718
|
+
affinities: [
|
|
2719
|
+
"circle",
|
|
2720
|
+
"seedOfLife",
|
|
2721
|
+
"flowerOfLife"
|
|
2722
|
+
],
|
|
2723
|
+
category: "sacred",
|
|
2724
|
+
heroCandidate: true,
|
|
2725
|
+
bestStyles: [
|
|
2726
|
+
"stroke-only",
|
|
2727
|
+
"watercolor"
|
|
2728
|
+
]
|
|
2729
|
+
},
|
|
2730
|
+
// ── Procedural shapes ─────────────────────────────────────────
|
|
2731
|
+
blob: {
|
|
2732
|
+
tier: 1,
|
|
2733
|
+
minSizeFraction: 0.05,
|
|
2734
|
+
maxSizeFraction: 1.0,
|
|
2735
|
+
affinities: [
|
|
2736
|
+
"blob",
|
|
2737
|
+
"circle",
|
|
2738
|
+
"superellipse",
|
|
2739
|
+
"waveRing"
|
|
2740
|
+
],
|
|
2741
|
+
category: "procedural",
|
|
2742
|
+
heroCandidate: false,
|
|
2743
|
+
bestStyles: [
|
|
2744
|
+
"fill-only",
|
|
2745
|
+
"watercolor",
|
|
2746
|
+
"fill-and-stroke"
|
|
2747
|
+
]
|
|
2748
|
+
},
|
|
2749
|
+
ngon: {
|
|
2750
|
+
tier: 2,
|
|
2751
|
+
minSizeFraction: 0.06,
|
|
2752
|
+
maxSizeFraction: 0.8,
|
|
2753
|
+
affinities: [
|
|
2754
|
+
"hexagon",
|
|
2755
|
+
"triangle",
|
|
2756
|
+
"diamond",
|
|
2757
|
+
"superellipse"
|
|
2758
|
+
],
|
|
2759
|
+
category: "procedural",
|
|
2760
|
+
heroCandidate: false,
|
|
2761
|
+
bestStyles: [
|
|
2762
|
+
"fill-and-stroke",
|
|
2763
|
+
"fill-only",
|
|
2764
|
+
"hatched"
|
|
2765
|
+
]
|
|
2766
|
+
},
|
|
2767
|
+
lissajous: {
|
|
2768
|
+
tier: 2,
|
|
2769
|
+
minSizeFraction: 0.15,
|
|
2770
|
+
maxSizeFraction: 0.8,
|
|
2771
|
+
affinities: [
|
|
2772
|
+
"spirograph",
|
|
2773
|
+
"rose",
|
|
2774
|
+
"celticKnot",
|
|
2775
|
+
"fibonacciSpiral"
|
|
2776
|
+
],
|
|
2777
|
+
category: "procedural",
|
|
2778
|
+
heroCandidate: false,
|
|
2779
|
+
bestStyles: [
|
|
2780
|
+
"stroke-only",
|
|
2781
|
+
"incomplete",
|
|
2782
|
+
"dashed"
|
|
2783
|
+
]
|
|
2784
|
+
},
|
|
2785
|
+
superellipse: {
|
|
2786
|
+
tier: 1,
|
|
2787
|
+
minSizeFraction: 0.05,
|
|
2788
|
+
maxSizeFraction: 1.0,
|
|
2789
|
+
affinities: [
|
|
2790
|
+
"circle",
|
|
2791
|
+
"square",
|
|
2792
|
+
"blob",
|
|
2793
|
+
"hexagon"
|
|
2794
|
+
],
|
|
2795
|
+
category: "procedural",
|
|
2796
|
+
heroCandidate: false,
|
|
2797
|
+
bestStyles: [
|
|
2798
|
+
"fill-only",
|
|
2799
|
+
"watercolor",
|
|
2800
|
+
"fill-and-stroke",
|
|
2801
|
+
"wood-grain"
|
|
2802
|
+
]
|
|
2803
|
+
},
|
|
2804
|
+
spirograph: {
|
|
2805
|
+
tier: 1,
|
|
2806
|
+
minSizeFraction: 0.15,
|
|
2807
|
+
maxSizeFraction: 0.9,
|
|
2808
|
+
affinities: [
|
|
2809
|
+
"rose",
|
|
2810
|
+
"lissajous",
|
|
2811
|
+
"mandala",
|
|
2812
|
+
"flowerOfLife"
|
|
2813
|
+
],
|
|
2814
|
+
category: "procedural",
|
|
2815
|
+
heroCandidate: true,
|
|
2816
|
+
bestStyles: [
|
|
2817
|
+
"stroke-only",
|
|
2818
|
+
"incomplete",
|
|
2819
|
+
"dashed"
|
|
2820
|
+
]
|
|
2821
|
+
},
|
|
2822
|
+
waveRing: {
|
|
2823
|
+
tier: 2,
|
|
2824
|
+
minSizeFraction: 0.1,
|
|
2825
|
+
maxSizeFraction: 0.8,
|
|
2826
|
+
affinities: [
|
|
2827
|
+
"circle",
|
|
2828
|
+
"blob",
|
|
2829
|
+
"torus",
|
|
2830
|
+
"spirograph"
|
|
2831
|
+
],
|
|
2832
|
+
category: "procedural",
|
|
2833
|
+
heroCandidate: false,
|
|
2834
|
+
bestStyles: [
|
|
2835
|
+
"stroke-only",
|
|
2836
|
+
"dashed",
|
|
2837
|
+
"incomplete"
|
|
2838
|
+
]
|
|
2839
|
+
},
|
|
2840
|
+
rose: {
|
|
2841
|
+
tier: 1,
|
|
2842
|
+
minSizeFraction: 0.1,
|
|
2843
|
+
maxSizeFraction: 0.9,
|
|
2844
|
+
affinities: [
|
|
2845
|
+
"spirograph",
|
|
2846
|
+
"mandala",
|
|
2847
|
+
"flowerOfLife",
|
|
2848
|
+
"circle"
|
|
2849
|
+
],
|
|
2850
|
+
category: "procedural",
|
|
2851
|
+
heroCandidate: true,
|
|
2852
|
+
bestStyles: [
|
|
2853
|
+
"stroke-only",
|
|
2854
|
+
"fill-only",
|
|
2855
|
+
"watercolor"
|
|
2856
|
+
]
|
|
2857
|
+
},
|
|
2858
|
+
// ── New procedural shapes ─────────────────────────────────────
|
|
2859
|
+
shardField: {
|
|
2860
|
+
tier: 2,
|
|
2861
|
+
minSizeFraction: 0.1,
|
|
2862
|
+
maxSizeFraction: 0.7,
|
|
2863
|
+
affinities: [
|
|
2864
|
+
"voronoiCell",
|
|
2865
|
+
"diamond",
|
|
2866
|
+
"triangle",
|
|
2867
|
+
"penroseTile"
|
|
2868
|
+
],
|
|
2869
|
+
category: "procedural",
|
|
2870
|
+
heroCandidate: false,
|
|
2871
|
+
bestStyles: [
|
|
2872
|
+
"fill-and-stroke",
|
|
2873
|
+
"stroke-only",
|
|
2874
|
+
"fill-only"
|
|
2875
|
+
]
|
|
2876
|
+
},
|
|
2877
|
+
voronoiCell: {
|
|
2878
|
+
tier: 1,
|
|
2879
|
+
minSizeFraction: 0.08,
|
|
2880
|
+
maxSizeFraction: 0.9,
|
|
2881
|
+
affinities: [
|
|
2882
|
+
"shardField",
|
|
2883
|
+
"ngon",
|
|
2884
|
+
"superellipse",
|
|
2885
|
+
"blob"
|
|
2886
|
+
],
|
|
2887
|
+
category: "procedural",
|
|
2888
|
+
heroCandidate: false,
|
|
2889
|
+
bestStyles: [
|
|
2890
|
+
"fill-and-stroke",
|
|
2891
|
+
"fill-only",
|
|
2892
|
+
"watercolor",
|
|
2893
|
+
"marble-vein"
|
|
2894
|
+
]
|
|
2895
|
+
},
|
|
2896
|
+
crescent: {
|
|
2897
|
+
tier: 1,
|
|
2898
|
+
minSizeFraction: 0.1,
|
|
2899
|
+
maxSizeFraction: 1.0,
|
|
2900
|
+
affinities: [
|
|
2901
|
+
"circle",
|
|
2902
|
+
"blob",
|
|
2903
|
+
"cloudForm",
|
|
2904
|
+
"vesicaPiscis"
|
|
2905
|
+
],
|
|
2906
|
+
category: "procedural",
|
|
2907
|
+
heroCandidate: true,
|
|
2908
|
+
bestStyles: [
|
|
2909
|
+
"fill-only",
|
|
2910
|
+
"watercolor",
|
|
2911
|
+
"fill-and-stroke"
|
|
2912
|
+
]
|
|
2913
|
+
},
|
|
2914
|
+
tendril: {
|
|
2915
|
+
tier: 2,
|
|
2916
|
+
minSizeFraction: 0.1,
|
|
2917
|
+
maxSizeFraction: 0.8,
|
|
2918
|
+
affinities: [
|
|
2919
|
+
"blob",
|
|
2920
|
+
"inkSplat",
|
|
2921
|
+
"lissajous",
|
|
2922
|
+
"fibonacciSpiral"
|
|
2923
|
+
],
|
|
2924
|
+
category: "procedural",
|
|
2925
|
+
heroCandidate: false,
|
|
2926
|
+
bestStyles: [
|
|
2927
|
+
"fill-only",
|
|
2928
|
+
"watercolor",
|
|
2929
|
+
"fill-and-stroke"
|
|
2930
|
+
]
|
|
2931
|
+
},
|
|
2932
|
+
cloudForm: {
|
|
2933
|
+
tier: 1,
|
|
2934
|
+
minSizeFraction: 0.15,
|
|
2935
|
+
maxSizeFraction: 1.0,
|
|
2936
|
+
affinities: [
|
|
2937
|
+
"blob",
|
|
2938
|
+
"circle",
|
|
2939
|
+
"crescent",
|
|
2940
|
+
"superellipse"
|
|
2941
|
+
],
|
|
2942
|
+
category: "procedural",
|
|
2943
|
+
heroCandidate: false,
|
|
2944
|
+
bestStyles: [
|
|
2945
|
+
"fill-only",
|
|
2946
|
+
"watercolor"
|
|
2947
|
+
]
|
|
2948
|
+
},
|
|
2949
|
+
inkSplat: {
|
|
2950
|
+
tier: 2,
|
|
2951
|
+
minSizeFraction: 0.1,
|
|
2952
|
+
maxSizeFraction: 0.8,
|
|
2953
|
+
affinities: [
|
|
2954
|
+
"blob",
|
|
2955
|
+
"tendril",
|
|
2956
|
+
"shardField",
|
|
2957
|
+
"star"
|
|
2958
|
+
],
|
|
2959
|
+
category: "procedural",
|
|
2960
|
+
heroCandidate: false,
|
|
2961
|
+
bestStyles: [
|
|
2962
|
+
"fill-only",
|
|
2963
|
+
"watercolor",
|
|
2964
|
+
"fill-and-stroke"
|
|
2965
|
+
]
|
|
2966
|
+
},
|
|
2967
|
+
geodesicDome: {
|
|
2968
|
+
tier: 2,
|
|
2969
|
+
minSizeFraction: 0.2,
|
|
2970
|
+
maxSizeFraction: 0.9,
|
|
2971
|
+
affinities: [
|
|
2972
|
+
"metatronsCube",
|
|
2973
|
+
"platonicSolid",
|
|
2974
|
+
"hexagon",
|
|
2975
|
+
"triangle"
|
|
2976
|
+
],
|
|
2977
|
+
category: "procedural",
|
|
2978
|
+
heroCandidate: true,
|
|
2979
|
+
bestStyles: [
|
|
2980
|
+
"stroke-only",
|
|
2981
|
+
"dashed",
|
|
2982
|
+
"double-stroke"
|
|
2983
|
+
]
|
|
2984
|
+
},
|
|
2985
|
+
penroseTile: {
|
|
2986
|
+
tier: 2,
|
|
2987
|
+
minSizeFraction: 0.06,
|
|
2988
|
+
maxSizeFraction: 0.6,
|
|
2989
|
+
affinities: [
|
|
2990
|
+
"diamond",
|
|
2991
|
+
"triangle",
|
|
2992
|
+
"shardField",
|
|
2993
|
+
"voronoiCell"
|
|
2994
|
+
],
|
|
2995
|
+
category: "procedural",
|
|
2996
|
+
heroCandidate: false,
|
|
2997
|
+
bestStyles: [
|
|
2998
|
+
"fill-and-stroke",
|
|
2999
|
+
"fill-only",
|
|
3000
|
+
"double-stroke"
|
|
3001
|
+
]
|
|
3002
|
+
},
|
|
3003
|
+
reuleauxTriangle: {
|
|
3004
|
+
tier: 1,
|
|
3005
|
+
minSizeFraction: 0.08,
|
|
3006
|
+
maxSizeFraction: 1.0,
|
|
3007
|
+
affinities: [
|
|
3008
|
+
"triangle",
|
|
3009
|
+
"circle",
|
|
3010
|
+
"superellipse",
|
|
3011
|
+
"vesicaPiscis"
|
|
3012
|
+
],
|
|
3013
|
+
category: "procedural",
|
|
3014
|
+
heroCandidate: true,
|
|
3015
|
+
bestStyles: [
|
|
3016
|
+
"fill-and-stroke",
|
|
3017
|
+
"fill-only",
|
|
3018
|
+
"watercolor"
|
|
3019
|
+
]
|
|
3020
|
+
},
|
|
3021
|
+
dotCluster: {
|
|
3022
|
+
tier: 3,
|
|
3023
|
+
minSizeFraction: 0.05,
|
|
3024
|
+
maxSizeFraction: 0.5,
|
|
3025
|
+
affinities: [
|
|
3026
|
+
"cloudForm",
|
|
3027
|
+
"inkSplat",
|
|
3028
|
+
"blob"
|
|
3029
|
+
],
|
|
3030
|
+
category: "procedural",
|
|
3031
|
+
heroCandidate: false,
|
|
3032
|
+
bestStyles: [
|
|
3033
|
+
"fill-only",
|
|
3034
|
+
"stipple"
|
|
3035
|
+
]
|
|
3036
|
+
},
|
|
3037
|
+
crosshatchPatch: {
|
|
3038
|
+
tier: 3,
|
|
3039
|
+
minSizeFraction: 0.1,
|
|
3040
|
+
maxSizeFraction: 0.6,
|
|
3041
|
+
affinities: [
|
|
3042
|
+
"voronoiCell",
|
|
3043
|
+
"ngon",
|
|
3044
|
+
"superellipse"
|
|
3045
|
+
],
|
|
3046
|
+
category: "procedural",
|
|
3047
|
+
heroCandidate: false,
|
|
3048
|
+
bestStyles: [
|
|
3049
|
+
"stroke-only",
|
|
3050
|
+
"hatched",
|
|
3051
|
+
"fabric-weave"
|
|
3052
|
+
]
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
function $24064302523652b1$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
|
|
3056
|
+
const available = shapeNames.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]);
|
|
3057
|
+
// Pick a seed shape — tier 1 shapes that are hero candidates
|
|
3058
|
+
const heroPool = available.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s].tier === 1 && $24064302523652b1$export$4343b39fe47bd82c[s].heroCandidate);
|
|
3059
|
+
const seedShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : available[Math.floor(rng() * available.length)];
|
|
3060
|
+
const seedProfile = $24064302523652b1$export$4343b39fe47bd82c[seedShape];
|
|
3061
|
+
// Primary: seed shape + its direct affinities (tier 1-2 only)
|
|
3062
|
+
const primaryCandidates = [
|
|
3063
|
+
seedShape,
|
|
3064
|
+
...seedProfile.affinities
|
|
3065
|
+
].filter((s)=>available.includes(s)).filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s].tier <= 2);
|
|
3066
|
+
const primary = [
|
|
3067
|
+
...new Set(primaryCandidates)
|
|
3068
|
+
].slice(0, 5);
|
|
3069
|
+
// Supporting: affinities of affinities, plus same-category shapes
|
|
3070
|
+
const supportingSet = new Set();
|
|
3071
|
+
for (const p of primary){
|
|
3072
|
+
const profile = $24064302523652b1$export$4343b39fe47bd82c[p];
|
|
3073
|
+
if (!profile) continue;
|
|
3074
|
+
for (const aff of profile.affinities)if (available.includes(aff) && !primary.includes(aff)) supportingSet.add(aff);
|
|
3075
|
+
}
|
|
3076
|
+
// Add same-category tier 1-2 shapes
|
|
3077
|
+
for (const s of available){
|
|
3078
|
+
const p = $24064302523652b1$export$4343b39fe47bd82c[s];
|
|
3079
|
+
if (p.category === seedProfile.category && p.tier <= 2 && !primary.includes(s)) supportingSet.add(s);
|
|
3080
|
+
}
|
|
3081
|
+
const supporting = [
|
|
3082
|
+
...supportingSet
|
|
3083
|
+
].slice(0, 6);
|
|
3084
|
+
// Accents: tier 1 shapes from other categories for contrast
|
|
3085
|
+
const usedCategories = new Set([
|
|
3086
|
+
...primary,
|
|
3087
|
+
...supporting
|
|
3088
|
+
].map((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]?.category));
|
|
3089
|
+
const accentCandidates = available.filter((s)=>!primary.includes(s) && !supporting.includes(s) && $24064302523652b1$export$4343b39fe47bd82c[s].tier <= 2 && !usedCategories.has($24064302523652b1$export$4343b39fe47bd82c[s].category));
|
|
3090
|
+
// Shuffle and take a few
|
|
3091
|
+
const accents = [];
|
|
3092
|
+
const shuffled = [
|
|
3093
|
+
...accentCandidates
|
|
3094
|
+
];
|
|
3095
|
+
for(let i = shuffled.length - 1; i > 0; i--){
|
|
3096
|
+
const j = Math.floor(rng() * (i + 1));
|
|
3097
|
+
[shuffled[i], shuffled[j]] = [
|
|
3098
|
+
shuffled[j],
|
|
3099
|
+
shuffled[i]
|
|
3100
|
+
];
|
|
3101
|
+
}
|
|
3102
|
+
accents.push(...shuffled.slice(0, 3));
|
|
3103
|
+
// For certain archetypes, bias the palette
|
|
3104
|
+
if (archetypeName === "geometric-precision") // Remove blobs and organic shapes from primary
|
|
3105
|
+
return {
|
|
3106
|
+
primary: primary.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]?.category !== "procedural" || s === "ngon"),
|
|
3107
|
+
supporting: supporting.filter((s)=>s !== "blob"),
|
|
3108
|
+
accents: accents
|
|
3109
|
+
};
|
|
3110
|
+
if (archetypeName === "organic-flow") {
|
|
3111
|
+
// Boost procedural/organic shapes
|
|
3112
|
+
const organicBoost = available.filter((s)=>[
|
|
3113
|
+
"blob",
|
|
3114
|
+
"superellipse",
|
|
3115
|
+
"waveRing",
|
|
3116
|
+
"rose"
|
|
3117
|
+
].includes(s) && !primary.includes(s));
|
|
3118
|
+
return {
|
|
3119
|
+
primary: [
|
|
3120
|
+
...primary,
|
|
3121
|
+
...organicBoost.slice(0, 2)
|
|
3122
|
+
],
|
|
3123
|
+
supporting: supporting,
|
|
3124
|
+
accents: accents
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
if (archetypeName === "shattered-glass") {
|
|
3128
|
+
// Favor angular, fragmented shapes
|
|
3129
|
+
const shardBoost = available.filter((s)=>[
|
|
3130
|
+
"shardField",
|
|
3131
|
+
"voronoiCell",
|
|
3132
|
+
"penroseTile",
|
|
3133
|
+
"diamond",
|
|
3134
|
+
"triangle",
|
|
3135
|
+
"ngon"
|
|
3136
|
+
].includes(s) && !primary.includes(s));
|
|
3137
|
+
return {
|
|
3138
|
+
primary: [
|
|
3139
|
+
...primary.filter((s)=>s !== "blob" && s !== "cloudForm"),
|
|
3140
|
+
...shardBoost.slice(0, 3)
|
|
3141
|
+
],
|
|
3142
|
+
supporting: supporting.filter((s)=>s !== "blob" && s !== "cloudForm"),
|
|
3143
|
+
accents: accents
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
if (archetypeName === "botanical") {
|
|
3147
|
+
// Favor organic, flowing shapes
|
|
3148
|
+
const botanicalBoost = available.filter((s)=>[
|
|
3149
|
+
"tendril",
|
|
3150
|
+
"cloudForm",
|
|
3151
|
+
"blob",
|
|
3152
|
+
"crescent",
|
|
3153
|
+
"rose",
|
|
3154
|
+
"inkSplat"
|
|
3155
|
+
].includes(s) && !primary.includes(s));
|
|
3156
|
+
return {
|
|
3157
|
+
primary: [
|
|
3158
|
+
...primary,
|
|
3159
|
+
...botanicalBoost.slice(0, 3)
|
|
3160
|
+
],
|
|
3161
|
+
supporting: supporting,
|
|
3162
|
+
accents: accents
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
if (archetypeName === "stipple-portrait") {
|
|
3166
|
+
// Favor small, dot-friendly shapes
|
|
3167
|
+
const stippleBoost = available.filter((s)=>[
|
|
3168
|
+
"dotCluster",
|
|
3169
|
+
"circle",
|
|
3170
|
+
"crosshatchPatch",
|
|
3171
|
+
"voronoiCell",
|
|
3172
|
+
"blob"
|
|
3173
|
+
].includes(s) && !primary.includes(s));
|
|
3174
|
+
return {
|
|
3175
|
+
primary: [
|
|
3176
|
+
...primary,
|
|
3177
|
+
...stippleBoost.slice(0, 3)
|
|
3178
|
+
],
|
|
3179
|
+
supporting: supporting,
|
|
3180
|
+
accents: accents
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
if (archetypeName === "celestial") {
|
|
3184
|
+
// Favor sacred geometry and cosmic shapes
|
|
3185
|
+
const celestialBoost = available.filter((s)=>[
|
|
3186
|
+
"crescent",
|
|
3187
|
+
"geodesicDome",
|
|
3188
|
+
"mandala",
|
|
3189
|
+
"flowerOfLife",
|
|
3190
|
+
"spirograph",
|
|
3191
|
+
"fibonacciSpiral"
|
|
3192
|
+
].includes(s) && !primary.includes(s));
|
|
3193
|
+
return {
|
|
3194
|
+
primary: [
|
|
3195
|
+
...primary,
|
|
3196
|
+
...celestialBoost.slice(0, 3)
|
|
3197
|
+
],
|
|
3198
|
+
supporting: supporting,
|
|
3199
|
+
accents: accents
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
return {
|
|
3203
|
+
primary: primary,
|
|
3204
|
+
supporting: supporting,
|
|
3205
|
+
accents: accents
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
function $24064302523652b1$export$3c37d9a045754d0e(palette, rng, sizeFraction) {
|
|
3209
|
+
// Filter each tier by size constraints
|
|
3210
|
+
const validPrimary = palette.primary.filter((s)=>{
|
|
3211
|
+
const p = $24064302523652b1$export$4343b39fe47bd82c[s];
|
|
3212
|
+
return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
|
|
3213
|
+
});
|
|
3214
|
+
const validSupporting = palette.supporting.filter((s)=>{
|
|
3215
|
+
const p = $24064302523652b1$export$4343b39fe47bd82c[s];
|
|
3216
|
+
return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
|
|
3217
|
+
});
|
|
3218
|
+
const validAccents = palette.accents.filter((s)=>{
|
|
3219
|
+
const p = $24064302523652b1$export$4343b39fe47bd82c[s];
|
|
3220
|
+
return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
|
|
3221
|
+
});
|
|
3222
|
+
const roll = rng();
|
|
3223
|
+
if (roll < 0.60 && validPrimary.length > 0) return validPrimary[Math.floor(rng() * validPrimary.length)];
|
|
3224
|
+
if (roll < 0.90 && validSupporting.length > 0) return validSupporting[Math.floor(rng() * validSupporting.length)];
|
|
3225
|
+
if (validAccents.length > 0) return validAccents[Math.floor(rng() * validAccents.length)];
|
|
3226
|
+
// Fallback: any valid primary or supporting
|
|
3227
|
+
const fallback = [
|
|
3228
|
+
...validPrimary,
|
|
3229
|
+
...validSupporting
|
|
3230
|
+
];
|
|
3231
|
+
if (fallback.length > 0) return fallback[Math.floor(rng() * fallback.length)];
|
|
3232
|
+
// Ultimate fallback
|
|
3233
|
+
return palette.primary[0] || "circle";
|
|
3234
|
+
}
|
|
3235
|
+
function $24064302523652b1$export$ab873bb6fb56c1a8(shapeName, layerStyle, rng) {
|
|
3236
|
+
const profile = $24064302523652b1$export$4343b39fe47bd82c[shapeName];
|
|
3237
|
+
if (!profile || rng() > 0.7) return layerStyle;
|
|
3238
|
+
return profile.bestStyles[Math.floor(rng() * profile.bestStyles.length)];
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
|
|
3242
|
+
|
|
3243
|
+
/**
|
|
3244
|
+
* Configuration options for image generation.
|
|
3245
|
+
*/ const $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f = {
|
|
3246
|
+
width: 2048,
|
|
3247
|
+
height: 2048,
|
|
3248
|
+
gridSize: 5,
|
|
3249
|
+
layers: 4,
|
|
3250
|
+
minShapeSize: 30,
|
|
3251
|
+
maxShapeSize: 400,
|
|
3252
|
+
baseOpacity: 0.7,
|
|
3253
|
+
opacityReduction: 0.12,
|
|
3254
|
+
shapesPerLayer: 0
|
|
3255
|
+
};
|
|
3256
|
+
|
|
3257
|
+
|
|
3258
|
+
/**
|
|
3259
|
+
* Visual archetypes — fundamentally different rendering personalities
|
|
3260
|
+
* selected deterministically from the hash.
|
|
3261
|
+
*
|
|
3262
|
+
* Each archetype overrides key rendering parameters to produce images
|
|
3263
|
+
* that look like they came from different generators entirely.
|
|
3264
|
+
*/ // ── Archetype definitions ───────────────────────────────────────────
|
|
3265
|
+
const $3faa2521b78398cf$var$ARCHETYPES = [
|
|
3266
|
+
{
|
|
3267
|
+
name: "dense-chaotic",
|
|
3268
|
+
gridSize: 9,
|
|
3269
|
+
layers: 5,
|
|
3270
|
+
baseOpacity: 0.5,
|
|
3271
|
+
opacityReduction: 0.06,
|
|
3272
|
+
minShapeSize: 10,
|
|
3273
|
+
maxShapeSize: 200,
|
|
3274
|
+
backgroundStyle: "radial-dark",
|
|
3275
|
+
paletteMode: "harmonious",
|
|
3276
|
+
preferredStyles: [
|
|
3277
|
+
"fill-and-stroke",
|
|
3278
|
+
"watercolor",
|
|
3279
|
+
"fill-only"
|
|
3280
|
+
],
|
|
3281
|
+
flowLineMultiplier: 2.5,
|
|
3282
|
+
heroShape: false,
|
|
3283
|
+
glowMultiplier: 0.5,
|
|
3284
|
+
sizePower: 1.2,
|
|
3285
|
+
invertForeground: false
|
|
3286
|
+
},
|
|
3287
|
+
{
|
|
3288
|
+
name: "minimal-spacious",
|
|
3289
|
+
gridSize: 2,
|
|
3290
|
+
layers: 2,
|
|
3291
|
+
baseOpacity: 0.85,
|
|
3292
|
+
opacityReduction: 0.05,
|
|
3293
|
+
minShapeSize: 150,
|
|
3294
|
+
maxShapeSize: 600,
|
|
3295
|
+
backgroundStyle: "solid-light",
|
|
3296
|
+
paletteMode: "duotone",
|
|
3297
|
+
preferredStyles: [
|
|
3298
|
+
"fill-and-stroke",
|
|
3299
|
+
"stroke-only",
|
|
3300
|
+
"incomplete"
|
|
3301
|
+
],
|
|
3302
|
+
flowLineMultiplier: 0.3,
|
|
3303
|
+
heroShape: true,
|
|
3304
|
+
glowMultiplier: 0,
|
|
3305
|
+
sizePower: 0.8,
|
|
3306
|
+
invertForeground: false
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
name: "organic-flow",
|
|
1675
3310
|
gridSize: 4,
|
|
1676
3311
|
layers: 3,
|
|
1677
3312
|
baseOpacity: 0.4,
|
|
@@ -1818,6 +3453,69 @@ const $3faa2521b78398cf$var$ARCHETYPES = [
|
|
|
1818
3453
|
sizePower: 2.5,
|
|
1819
3454
|
invertForeground: false
|
|
1820
3455
|
},
|
|
3456
|
+
{
|
|
3457
|
+
name: "watercolor-wash",
|
|
3458
|
+
gridSize: 3,
|
|
3459
|
+
layers: 3,
|
|
3460
|
+
baseOpacity: 0.25,
|
|
3461
|
+
opacityReduction: 0.03,
|
|
3462
|
+
minShapeSize: 200,
|
|
3463
|
+
maxShapeSize: 700,
|
|
3464
|
+
backgroundStyle: "radial-light",
|
|
3465
|
+
paletteMode: "harmonious",
|
|
3466
|
+
preferredStyles: [
|
|
3467
|
+
"watercolor",
|
|
3468
|
+
"fill-only",
|
|
3469
|
+
"incomplete"
|
|
3470
|
+
],
|
|
3471
|
+
flowLineMultiplier: 0.5,
|
|
3472
|
+
heroShape: false,
|
|
3473
|
+
glowMultiplier: 0.3,
|
|
3474
|
+
sizePower: 0.6,
|
|
3475
|
+
invertForeground: false
|
|
3476
|
+
},
|
|
3477
|
+
{
|
|
3478
|
+
name: "op-art",
|
|
3479
|
+
gridSize: 8,
|
|
3480
|
+
layers: 2,
|
|
3481
|
+
baseOpacity: 0.95,
|
|
3482
|
+
opacityReduction: 0.05,
|
|
3483
|
+
minShapeSize: 20,
|
|
3484
|
+
maxShapeSize: 200,
|
|
3485
|
+
backgroundStyle: "solid-light",
|
|
3486
|
+
paletteMode: "high-contrast",
|
|
3487
|
+
preferredStyles: [
|
|
3488
|
+
"fill-and-stroke",
|
|
3489
|
+
"stroke-only",
|
|
3490
|
+
"dashed"
|
|
3491
|
+
],
|
|
3492
|
+
flowLineMultiplier: 0,
|
|
3493
|
+
heroShape: false,
|
|
3494
|
+
glowMultiplier: 0,
|
|
3495
|
+
sizePower: 0.4,
|
|
3496
|
+
invertForeground: false
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
name: "collage",
|
|
3500
|
+
gridSize: 4,
|
|
3501
|
+
layers: 3,
|
|
3502
|
+
baseOpacity: 0.9,
|
|
3503
|
+
opacityReduction: 0.08,
|
|
3504
|
+
minShapeSize: 80,
|
|
3505
|
+
maxShapeSize: 500,
|
|
3506
|
+
backgroundStyle: "solid-light",
|
|
3507
|
+
paletteMode: "duotone",
|
|
3508
|
+
preferredStyles: [
|
|
3509
|
+
"fill-and-stroke",
|
|
3510
|
+
"fill-only",
|
|
3511
|
+
"double-stroke"
|
|
3512
|
+
],
|
|
3513
|
+
flowLineMultiplier: 0,
|
|
3514
|
+
heroShape: true,
|
|
3515
|
+
glowMultiplier: 0,
|
|
3516
|
+
sizePower: 0.7,
|
|
3517
|
+
invertForeground: false
|
|
3518
|
+
},
|
|
1821
3519
|
{
|
|
1822
3520
|
name: "classic",
|
|
1823
3521
|
gridSize: 5,
|
|
@@ -1838,6 +3536,91 @@ const $3faa2521b78398cf$var$ARCHETYPES = [
|
|
|
1838
3536
|
glowMultiplier: 1,
|
|
1839
3537
|
sizePower: 1.8,
|
|
1840
3538
|
invertForeground: false
|
|
3539
|
+
},
|
|
3540
|
+
{
|
|
3541
|
+
name: "shattered-glass",
|
|
3542
|
+
gridSize: 8,
|
|
3543
|
+
layers: 3,
|
|
3544
|
+
baseOpacity: 0.85,
|
|
3545
|
+
opacityReduction: 0.1,
|
|
3546
|
+
minShapeSize: 15,
|
|
3547
|
+
maxShapeSize: 250,
|
|
3548
|
+
backgroundStyle: "solid-dark",
|
|
3549
|
+
paletteMode: "high-contrast",
|
|
3550
|
+
preferredStyles: [
|
|
3551
|
+
"fill-and-stroke",
|
|
3552
|
+
"stroke-only",
|
|
3553
|
+
"fill-only"
|
|
3554
|
+
],
|
|
3555
|
+
flowLineMultiplier: 0,
|
|
3556
|
+
heroShape: false,
|
|
3557
|
+
glowMultiplier: 0.3,
|
|
3558
|
+
sizePower: 1.0,
|
|
3559
|
+
invertForeground: false
|
|
3560
|
+
},
|
|
3561
|
+
{
|
|
3562
|
+
name: "botanical",
|
|
3563
|
+
gridSize: 4,
|
|
3564
|
+
layers: 4,
|
|
3565
|
+
baseOpacity: 0.5,
|
|
3566
|
+
opacityReduction: 0.06,
|
|
3567
|
+
minShapeSize: 30,
|
|
3568
|
+
maxShapeSize: 400,
|
|
3569
|
+
backgroundStyle: "radial-light",
|
|
3570
|
+
paletteMode: "earth",
|
|
3571
|
+
preferredStyles: [
|
|
3572
|
+
"watercolor",
|
|
3573
|
+
"fill-only",
|
|
3574
|
+
"incomplete"
|
|
3575
|
+
],
|
|
3576
|
+
flowLineMultiplier: 3,
|
|
3577
|
+
heroShape: true,
|
|
3578
|
+
glowMultiplier: 0.2,
|
|
3579
|
+
sizePower: 1.6,
|
|
3580
|
+
invertForeground: false
|
|
3581
|
+
},
|
|
3582
|
+
{
|
|
3583
|
+
name: "stipple-portrait",
|
|
3584
|
+
gridSize: 9,
|
|
3585
|
+
layers: 2,
|
|
3586
|
+
baseOpacity: 0.8,
|
|
3587
|
+
opacityReduction: 0.05,
|
|
3588
|
+
minShapeSize: 5,
|
|
3589
|
+
maxShapeSize: 120,
|
|
3590
|
+
backgroundStyle: "solid-light",
|
|
3591
|
+
paletteMode: "monochrome",
|
|
3592
|
+
preferredStyles: [
|
|
3593
|
+
"stipple",
|
|
3594
|
+
"fill-only",
|
|
3595
|
+
"hatched"
|
|
3596
|
+
],
|
|
3597
|
+
flowLineMultiplier: 0,
|
|
3598
|
+
heroShape: false,
|
|
3599
|
+
glowMultiplier: 0,
|
|
3600
|
+
sizePower: 2.8,
|
|
3601
|
+
invertForeground: false
|
|
3602
|
+
},
|
|
3603
|
+
{
|
|
3604
|
+
name: "celestial",
|
|
3605
|
+
gridSize: 7,
|
|
3606
|
+
layers: 5,
|
|
3607
|
+
baseOpacity: 0.45,
|
|
3608
|
+
opacityReduction: 0.04,
|
|
3609
|
+
minShapeSize: 8,
|
|
3610
|
+
maxShapeSize: 450,
|
|
3611
|
+
backgroundStyle: "radial-dark",
|
|
3612
|
+
paletteMode: "neon",
|
|
3613
|
+
preferredStyles: [
|
|
3614
|
+
"fill-only",
|
|
3615
|
+
"watercolor",
|
|
3616
|
+
"stroke-only",
|
|
3617
|
+
"incomplete"
|
|
3618
|
+
],
|
|
3619
|
+
flowLineMultiplier: 2,
|
|
3620
|
+
heroShape: true,
|
|
3621
|
+
glowMultiplier: 2.5,
|
|
3622
|
+
sizePower: 2.2,
|
|
3623
|
+
invertForeground: false
|
|
1841
3624
|
}
|
|
1842
3625
|
];
|
|
1843
3626
|
function $3faa2521b78398cf$export$f1142fd7da4d6590(rng) {
|
|
@@ -1845,26 +3628,7 @@ function $3faa2521b78398cf$export$f1142fd7da4d6590(rng) {
|
|
|
1845
3628
|
}
|
|
1846
3629
|
|
|
1847
3630
|
|
|
1848
|
-
// ── Shape categories for weighted selection
|
|
1849
|
-
const $b623126c6e9cbb71$var$BASIC_SHAPES = [
|
|
1850
|
-
"circle",
|
|
1851
|
-
"square",
|
|
1852
|
-
"triangle",
|
|
1853
|
-
"hexagon",
|
|
1854
|
-
"diamond",
|
|
1855
|
-
"cube"
|
|
1856
|
-
];
|
|
1857
|
-
const $b623126c6e9cbb71$var$COMPLEX_SHAPES = [
|
|
1858
|
-
"star",
|
|
1859
|
-
"jacked-star",
|
|
1860
|
-
"heart",
|
|
1861
|
-
"platonicSolid",
|
|
1862
|
-
"fibonacciSpiral",
|
|
1863
|
-
"islamicPattern",
|
|
1864
|
-
"celticKnot",
|
|
1865
|
-
"merkaba",
|
|
1866
|
-
"fractal"
|
|
1867
|
-
];
|
|
3631
|
+
// ── Shape categories for weighted selection (legacy fallback) ───────
|
|
1868
3632
|
const $b623126c6e9cbb71$var$SACRED_SHAPES = [
|
|
1869
3633
|
"mandala",
|
|
1870
3634
|
"flowerOfLife",
|
|
@@ -1876,15 +3640,6 @@ const $b623126c6e9cbb71$var$SACRED_SHAPES = [
|
|
|
1876
3640
|
"torus",
|
|
1877
3641
|
"eggOfLife"
|
|
1878
3642
|
];
|
|
1879
|
-
const $b623126c6e9cbb71$var$PROCEDURAL_SHAPES = [
|
|
1880
|
-
"blob",
|
|
1881
|
-
"ngon",
|
|
1882
|
-
"lissajous",
|
|
1883
|
-
"superellipse",
|
|
1884
|
-
"spirograph",
|
|
1885
|
-
"waveRing",
|
|
1886
|
-
"rose"
|
|
1887
|
-
];
|
|
1888
3643
|
const $b623126c6e9cbb71$var$COMPOSITION_MODES = [
|
|
1889
3644
|
"radial",
|
|
1890
3645
|
"flow-field",
|
|
@@ -1892,23 +3647,6 @@ const $b623126c6e9cbb71$var$COMPOSITION_MODES = [
|
|
|
1892
3647
|
"grid-subdivision",
|
|
1893
3648
|
"clustered"
|
|
1894
3649
|
];
|
|
1895
|
-
// ── Helper: pick shape with layer-aware weighting ───────────────────
|
|
1896
|
-
function $b623126c6e9cbb71$var$pickShape(rng, layerRatio, shapeNames) {
|
|
1897
|
-
const basicW = 1 - layerRatio * 0.6;
|
|
1898
|
-
const complexW = 0.3 + layerRatio * 0.3;
|
|
1899
|
-
const sacredW = 0.1 + layerRatio * 0.4;
|
|
1900
|
-
const proceduralW = 0.25 + layerRatio * 0.2; // always present, grows with depth
|
|
1901
|
-
const total = basicW + complexW + sacredW + proceduralW;
|
|
1902
|
-
const roll = rng() * total;
|
|
1903
|
-
let pool;
|
|
1904
|
-
if (roll < basicW) pool = $b623126c6e9cbb71$var$BASIC_SHAPES;
|
|
1905
|
-
else if (roll < basicW + complexW) pool = $b623126c6e9cbb71$var$COMPLEX_SHAPES;
|
|
1906
|
-
else if (roll < basicW + complexW + sacredW) pool = $b623126c6e9cbb71$var$SACRED_SHAPES;
|
|
1907
|
-
else pool = $b623126c6e9cbb71$var$PROCEDURAL_SHAPES;
|
|
1908
|
-
const available = pool.filter((s)=>shapeNames.includes(s));
|
|
1909
|
-
if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
1910
|
-
return available[Math.floor(rng() * available.length)];
|
|
1911
|
-
}
|
|
1912
3650
|
// ── Helper: get position based on composition mode ──────────────────
|
|
1913
3651
|
function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
1914
3652
|
switch(mode){
|
|
@@ -1967,22 +3705,28 @@ function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1967
3705
|
};
|
|
1968
3706
|
}
|
|
1969
3707
|
}
|
|
1970
|
-
// ── Helper: positional color
|
|
1971
|
-
function $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height,
|
|
1972
|
-
|
|
1973
|
-
const
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
return (0, $9d614e7d77fc2947$export$
|
|
3708
|
+
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
3709
|
+
function $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, hierarchy, rng) {
|
|
3710
|
+
// Blend position into color selection — shapes near center lean dominant
|
|
3711
|
+
const distFromCenter = Math.hypot(x - width / 2, y - height / 2) / Math.hypot(width / 2, height / 2);
|
|
3712
|
+
// Center = more dominant, edges = more accent
|
|
3713
|
+
if (distFromCenter < 0.35) return (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(hierarchy.dominant, rng, 10, 0.08);
|
|
3714
|
+
else if (distFromCenter < 0.7) return (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(hierarchy, rng), rng, 8, 0.06);
|
|
3715
|
+
else {
|
|
3716
|
+
// Edges: bias toward secondary/accent
|
|
3717
|
+
const roll = rng();
|
|
3718
|
+
const color = roll < 0.4 ? hierarchy.secondary : roll < 0.75 ? hierarchy.accent : hierarchy.dominant;
|
|
3719
|
+
return (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(color, rng, 12, 0.08);
|
|
3720
|
+
}
|
|
1977
3721
|
}
|
|
1978
|
-
// ── Helper: check if a position is inside a void zone
|
|
3722
|
+
// ── Helper: check if a position is inside a void zone ───────────────
|
|
1979
3723
|
function $b623126c6e9cbb71$var$isInVoidZone(x, y, voidZones) {
|
|
1980
3724
|
for (const zone of voidZones){
|
|
1981
3725
|
if (Math.hypot(x - zone.x, y - zone.y) < zone.radius) return true;
|
|
1982
3726
|
}
|
|
1983
3727
|
return false;
|
|
1984
3728
|
}
|
|
1985
|
-
// ── Helper: density check
|
|
3729
|
+
// ── Helper: density check ───────────────────────────────────────────
|
|
1986
3730
|
function $b623126c6e9cbb71$var$localDensity(x, y, positions, radius) {
|
|
1987
3731
|
let count = 0;
|
|
1988
3732
|
for (const p of positions)if (Math.hypot(x - p.x, y - p.y) < radius) count++;
|
|
@@ -2056,6 +3800,135 @@ function $b623126c6e9cbb71$var$drawBackground(ctx, style, bgStart, bgEnd, width,
|
|
|
2056
3800
|
}
|
|
2057
3801
|
}
|
|
2058
3802
|
}
|
|
3803
|
+
const $b623126c6e9cbb71$var$CONSTELLATIONS = [
|
|
3804
|
+
{
|
|
3805
|
+
name: "flanked-triangle",
|
|
3806
|
+
build: (rng, baseSize)=>{
|
|
3807
|
+
const gap = baseSize * (0.6 + rng() * 0.3);
|
|
3808
|
+
return [
|
|
3809
|
+
{
|
|
3810
|
+
dx: 0,
|
|
3811
|
+
dy: 0,
|
|
3812
|
+
shape: "triangle",
|
|
3813
|
+
size: baseSize,
|
|
3814
|
+
rotation: rng() * 360
|
|
3815
|
+
},
|
|
3816
|
+
{
|
|
3817
|
+
dx: -gap,
|
|
3818
|
+
dy: gap * 0.3,
|
|
3819
|
+
shape: "circle",
|
|
3820
|
+
size: baseSize * 0.35,
|
|
3821
|
+
rotation: 0
|
|
3822
|
+
},
|
|
3823
|
+
{
|
|
3824
|
+
dx: gap,
|
|
3825
|
+
dy: gap * 0.3,
|
|
3826
|
+
shape: "circle",
|
|
3827
|
+
size: baseSize * 0.35,
|
|
3828
|
+
rotation: 0
|
|
3829
|
+
}
|
|
3830
|
+
];
|
|
3831
|
+
}
|
|
3832
|
+
},
|
|
3833
|
+
{
|
|
3834
|
+
name: "hexagon-ring",
|
|
3835
|
+
build: (rng, baseSize)=>{
|
|
3836
|
+
const members = [];
|
|
3837
|
+
const count = 5 + Math.floor(rng() * 2);
|
|
3838
|
+
const ringR = baseSize * 0.6;
|
|
3839
|
+
for(let i = 0; i < count; i++){
|
|
3840
|
+
const angle = i / count * Math.PI * 2;
|
|
3841
|
+
members.push({
|
|
3842
|
+
dx: Math.cos(angle) * ringR,
|
|
3843
|
+
dy: Math.sin(angle) * ringR,
|
|
3844
|
+
shape: "hexagon",
|
|
3845
|
+
size: baseSize * (0.25 + rng() * 0.1),
|
|
3846
|
+
rotation: angle * 180 / Math.PI
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
return members;
|
|
3850
|
+
}
|
|
3851
|
+
},
|
|
3852
|
+
{
|
|
3853
|
+
name: "spiral-dots",
|
|
3854
|
+
build: (rng, baseSize)=>{
|
|
3855
|
+
const members = [];
|
|
3856
|
+
const count = 7 + Math.floor(rng() * 5);
|
|
3857
|
+
const turns = 1.5 + rng();
|
|
3858
|
+
for(let i = 0; i < count; i++){
|
|
3859
|
+
const t = i / count;
|
|
3860
|
+
const angle = t * Math.PI * 2 * turns;
|
|
3861
|
+
const r = t * baseSize * 0.7;
|
|
3862
|
+
members.push({
|
|
3863
|
+
dx: Math.cos(angle) * r,
|
|
3864
|
+
dy: Math.sin(angle) * r,
|
|
3865
|
+
shape: "circle",
|
|
3866
|
+
size: baseSize * (0.08 + (1 - t) * 0.12),
|
|
3867
|
+
rotation: 0
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
return members;
|
|
3871
|
+
}
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
name: "diamond-cluster",
|
|
3875
|
+
build: (rng, baseSize)=>{
|
|
3876
|
+
const gap = baseSize * 0.45;
|
|
3877
|
+
return [
|
|
3878
|
+
{
|
|
3879
|
+
dx: 0,
|
|
3880
|
+
dy: -gap,
|
|
3881
|
+
shape: "diamond",
|
|
3882
|
+
size: baseSize * 0.4,
|
|
3883
|
+
rotation: 0
|
|
3884
|
+
},
|
|
3885
|
+
{
|
|
3886
|
+
dx: gap,
|
|
3887
|
+
dy: 0,
|
|
3888
|
+
shape: "diamond",
|
|
3889
|
+
size: baseSize * 0.35,
|
|
3890
|
+
rotation: 15
|
|
3891
|
+
},
|
|
3892
|
+
{
|
|
3893
|
+
dx: 0,
|
|
3894
|
+
dy: gap,
|
|
3895
|
+
shape: "diamond",
|
|
3896
|
+
size: baseSize * 0.3,
|
|
3897
|
+
rotation: 30
|
|
3898
|
+
},
|
|
3899
|
+
{
|
|
3900
|
+
dx: -gap,
|
|
3901
|
+
dy: 0,
|
|
3902
|
+
shape: "diamond",
|
|
3903
|
+
size: baseSize * 0.35,
|
|
3904
|
+
rotation: -15
|
|
3905
|
+
}
|
|
3906
|
+
];
|
|
3907
|
+
}
|
|
3908
|
+
},
|
|
3909
|
+
{
|
|
3910
|
+
name: "crescent-pair",
|
|
3911
|
+
build: (rng, baseSize)=>{
|
|
3912
|
+
const gap = baseSize * 0.5;
|
|
3913
|
+
return [
|
|
3914
|
+
{
|
|
3915
|
+
dx: -gap * 0.4,
|
|
3916
|
+
dy: 0,
|
|
3917
|
+
shape: "crescent",
|
|
3918
|
+
size: baseSize * 0.5,
|
|
3919
|
+
rotation: rng() * 30
|
|
3920
|
+
},
|
|
3921
|
+
{
|
|
3922
|
+
dx: gap * 0.4,
|
|
3923
|
+
dy: 0,
|
|
3924
|
+
shape: "crescent",
|
|
3925
|
+
size: baseSize * 0.45,
|
|
3926
|
+
rotation: 180 + rng() * 30
|
|
3927
|
+
}
|
|
3928
|
+
];
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
];
|
|
2059
3932
|
function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
2060
3933
|
const finalConfig = {
|
|
2061
3934
|
...(0, $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f),
|
|
@@ -2078,7 +3951,15 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2078
3951
|
const [bgStart, bgEnd] = colorScheme.getBackgroundColorsByMode(archetype.paletteMode);
|
|
2079
3952
|
const tempMode = colorScheme.getTemperatureMode();
|
|
2080
3953
|
const fgTempTarget = tempMode === "warm-bg" ? "cool" : tempMode === "cool-bg" ? "warm" : null;
|
|
3954
|
+
// ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
|
|
3955
|
+
const colorHierarchy = (0, $9d614e7d77fc2947$export$fabac4600b87056)(colors, rng);
|
|
3956
|
+
// ── 0c. Shape palette — curated shapes that work well together ──
|
|
2081
3957
|
const shapeNames = Object.keys((0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5));
|
|
3958
|
+
const shapePalette = (0, $24064302523652b1$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
|
|
3959
|
+
// ── 0d. Color grading — unified tone for the whole image ───────
|
|
3960
|
+
const colorGrade = (0, $9d614e7d77fc2947$export$6d1620b367f86f7a)(rng);
|
|
3961
|
+
// ── 0e. Light direction — consistent shadow angle ──────────────
|
|
3962
|
+
const lightAngle = rng() * Math.PI * 2;
|
|
2082
3963
|
const scaleFactor = Math.min(width, height) / 1024;
|
|
2083
3964
|
const adjustedMinSize = minShapeSize * scaleFactor;
|
|
2084
3965
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
@@ -2087,27 +3968,45 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2087
3968
|
// ── 1. Background ──────────────────────────────────────────────
|
|
2088
3969
|
const bgRadius = Math.hypot(cx, cy);
|
|
2089
3970
|
$b623126c6e9cbb71$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
|
|
3971
|
+
// Gradient mesh overlay — 3-4 color control points for richer backgrounds
|
|
3972
|
+
const meshPoints = 3 + Math.floor(rng() * 2);
|
|
3973
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
3974
|
+
for(let i = 0; i < meshPoints; i++){
|
|
3975
|
+
const mx = rng() * width;
|
|
3976
|
+
const my = rng() * height;
|
|
3977
|
+
const mRadius = Math.min(width, height) * (0.3 + rng() * 0.4);
|
|
3978
|
+
const mColor = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
3979
|
+
const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
|
|
3980
|
+
grad.addColorStop(0, (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
|
|
3981
|
+
grad.addColorStop(1, "rgba(0,0,0,0)");
|
|
3982
|
+
ctx.globalAlpha = 1;
|
|
3983
|
+
ctx.fillStyle = grad;
|
|
3984
|
+
ctx.fillRect(0, 0, width, height);
|
|
3985
|
+
}
|
|
3986
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2090
3987
|
// Compute average background luminance for contrast enforcement
|
|
2091
3988
|
const bgLum = ((0, $9d614e7d77fc2947$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $9d614e7d77fc2947$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
|
|
2092
|
-
// ── 1b. Layered background
|
|
2093
|
-
// Draw large, very faint shapes to give the background texture
|
|
3989
|
+
// ── 1b. Layered background — archetype-coherent shapes ─────────
|
|
2094
3990
|
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
2095
3991
|
ctx.globalCompositeOperation = "soft-light";
|
|
2096
3992
|
for(let i = 0; i < bgShapeCount; i++){
|
|
2097
3993
|
const bx = rng() * width;
|
|
2098
3994
|
const by = rng() * height;
|
|
2099
3995
|
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
2100
|
-
const bColor =
|
|
3996
|
+
const bColor = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
2101
3997
|
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
2102
3998
|
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(bColor, 0.15);
|
|
2103
3999
|
ctx.beginPath();
|
|
2104
|
-
|
|
4000
|
+
// Use archetype-appropriate background shapes
|
|
4001
|
+
if (archetype.name === "geometric-precision" || archetype.name === "op-art") // Rectangular shapes for geometric archetypes
|
|
4002
|
+
ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4003
|
+
else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
2105
4004
|
ctx.fill();
|
|
2106
4005
|
}
|
|
2107
4006
|
// Subtle concentric rings from center
|
|
2108
4007
|
const ringCount = 2 + Math.floor(rng() * 3);
|
|
2109
4008
|
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
2110
|
-
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(
|
|
4009
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
2111
4010
|
ctx.lineWidth = 1 * scaleFactor;
|
|
2112
4011
|
for(let i = 1; i <= ringCount; i++){
|
|
2113
4012
|
const r = Math.min(width, height) * 0.15 * i;
|
|
@@ -2116,12 +4015,70 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2116
4015
|
ctx.stroke();
|
|
2117
4016
|
}
|
|
2118
4017
|
ctx.globalCompositeOperation = "source-over";
|
|
4018
|
+
// ── 1c. Background pattern layer — subtle textured paper ───────
|
|
4019
|
+
const bgPatternRoll = rng();
|
|
4020
|
+
if (bgPatternRoll < 0.6) {
|
|
4021
|
+
ctx.save();
|
|
4022
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
4023
|
+
const patternOpacity = 0.02 + rng() * 0.04;
|
|
4024
|
+
const patternColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
|
|
4025
|
+
if (bgPatternRoll < 0.2) {
|
|
4026
|
+
// Dot grid
|
|
4027
|
+
const dotSpacing = Math.max(8, Math.min(width, height) * (0.015 + rng() * 0.015));
|
|
4028
|
+
const dotR = dotSpacing * 0.08;
|
|
4029
|
+
ctx.globalAlpha = patternOpacity;
|
|
4030
|
+
ctx.fillStyle = patternColor;
|
|
4031
|
+
for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
|
|
4032
|
+
ctx.beginPath();
|
|
4033
|
+
ctx.arc(px, py, dotR, 0, Math.PI * 2);
|
|
4034
|
+
ctx.fill();
|
|
4035
|
+
}
|
|
4036
|
+
} else if (bgPatternRoll < 0.4) {
|
|
4037
|
+
// Diagonal lines
|
|
4038
|
+
const lineSpacing = Math.max(6, Math.min(width, height) * (0.02 + rng() * 0.02));
|
|
4039
|
+
ctx.globalAlpha = patternOpacity;
|
|
4040
|
+
ctx.strokeStyle = patternColor;
|
|
4041
|
+
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4042
|
+
const diag = Math.hypot(width, height);
|
|
4043
|
+
for(let d = -diag; d < diag; d += lineSpacing){
|
|
4044
|
+
ctx.beginPath();
|
|
4045
|
+
ctx.moveTo(d, 0);
|
|
4046
|
+
ctx.lineTo(d + height, height);
|
|
4047
|
+
ctx.stroke();
|
|
4048
|
+
}
|
|
4049
|
+
} else {
|
|
4050
|
+
// Tessellation — hexagonal grid of tiny shapes
|
|
4051
|
+
const tessSize = Math.max(10, Math.min(width, height) * (0.025 + rng() * 0.02));
|
|
4052
|
+
const tessH = tessSize * Math.sqrt(3);
|
|
4053
|
+
ctx.globalAlpha = patternOpacity * 0.7;
|
|
4054
|
+
ctx.strokeStyle = patternColor;
|
|
4055
|
+
ctx.lineWidth = 0.4 * scaleFactor;
|
|
4056
|
+
for(let row = 0; row * tessH < height + tessH; row++){
|
|
4057
|
+
const offsetX = row % 2 * tessSize * 0.75;
|
|
4058
|
+
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
|
|
4059
|
+
const hx = col * tessSize * 1.5 + offsetX;
|
|
4060
|
+
const hy = row * tessH;
|
|
4061
|
+
ctx.beginPath();
|
|
4062
|
+
for(let s = 0; s < 6; s++){
|
|
4063
|
+
const angle = Math.PI / 3 * s - Math.PI / 6;
|
|
4064
|
+
const vx = hx + Math.cos(angle) * tessSize * 0.5;
|
|
4065
|
+
const vy = hy + Math.sin(angle) * tessSize * 0.5;
|
|
4066
|
+
if (s === 0) ctx.moveTo(vx, vy);
|
|
4067
|
+
else ctx.lineTo(vx, vy);
|
|
4068
|
+
}
|
|
4069
|
+
ctx.closePath();
|
|
4070
|
+
ctx.stroke();
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
ctx.restore();
|
|
4075
|
+
}
|
|
4076
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2119
4077
|
// ── 2. Composition mode ────────────────────────────────────────
|
|
2120
4078
|
const compositionMode = $b623126c6e9cbb71$var$COMPOSITION_MODES[Math.floor(rng() * $b623126c6e9cbb71$var$COMPOSITION_MODES.length)];
|
|
2121
4079
|
const symRoll = rng();
|
|
2122
4080
|
const symmetryMode = symRoll < 0.10 ? "bilateral-x" : symRoll < 0.20 ? "bilateral-y" : symRoll < 0.25 ? "quad" : "none";
|
|
2123
4081
|
// ── 3. Focal points + void zones ───────────────────────────────
|
|
2124
|
-
// Rule-of-thirds intersection points for intentional composition
|
|
2125
4082
|
const THIRDS_POINTS = [
|
|
2126
4083
|
{
|
|
2127
4084
|
x: 1 / 3,
|
|
@@ -2142,10 +4099,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2142
4099
|
];
|
|
2143
4100
|
const numFocal = 1 + Math.floor(rng() * 2);
|
|
2144
4101
|
const focalPoints = [];
|
|
2145
|
-
for(let f = 0; f < numFocal; f++)
|
|
2146
|
-
if (rng() < 0.7) {
|
|
4102
|
+
for(let f = 0; f < numFocal; f++)if (rng() < 0.7) {
|
|
2147
4103
|
const tp = THIRDS_POINTS[Math.floor(rng() * THIRDS_POINTS.length)];
|
|
2148
|
-
// Small jitter around the thirds point so it's not robotic
|
|
2149
4104
|
focalPoints.push({
|
|
2150
4105
|
x: width * (tp.x + (rng() - 0.5) * 0.08),
|
|
2151
4106
|
y: height * (tp.y + (rng() - 0.5) * 0.08),
|
|
@@ -2156,7 +4111,6 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2156
4111
|
y: height * (0.2 + rng() * 0.6),
|
|
2157
4112
|
strength: 0.3 + rng() * 0.4
|
|
2158
4113
|
});
|
|
2159
|
-
// Feature E: 1-2 void zones where shapes are sparse (negative space)
|
|
2160
4114
|
const numVoids = Math.floor(rng() * 2) + 1;
|
|
2161
4115
|
const voidZones = [];
|
|
2162
4116
|
for(let v = 0; v < numVoids; v++)voidZones.push({
|
|
@@ -2188,20 +4142,24 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2188
4142
|
}
|
|
2189
4143
|
// Track all placed shapes for density checks and connecting curves
|
|
2190
4144
|
const shapePositions = [];
|
|
4145
|
+
// Hero avoidance radius — shapes near the hero orient toward it
|
|
4146
|
+
let heroCenter = null;
|
|
2191
4147
|
// ── 4b. Hero shape — a dominant focal element ───────────────────
|
|
2192
4148
|
if (archetype.heroShape && rng() < 0.6) {
|
|
2193
4149
|
const heroFocal = focalPoints[0];
|
|
4150
|
+
// Use shape palette hero candidates
|
|
2194
4151
|
const heroPool = [
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
];
|
|
2200
|
-
const heroShape = heroPool.filter((s)=>shapeNames.includes(s))[Math.floor(rng() * heroPool.filter((s)=>shapeNames.includes(s)).length)] || shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
4152
|
+
...shapePalette.primary,
|
|
4153
|
+
...shapePalette.supporting
|
|
4154
|
+
].filter((s)=>(0, $24064302523652b1$export$4343b39fe47bd82c)[s]?.heroCandidate && shapeNames.includes(s));
|
|
4155
|
+
const heroShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
2201
4156
|
const heroSize = adjustedMaxSize * (0.8 + rng() * 0.5);
|
|
2202
4157
|
const heroRotation = rng() * 360;
|
|
2203
|
-
const heroFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$
|
|
2204
|
-
const heroStroke = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$
|
|
4158
|
+
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);
|
|
4159
|
+
const heroStroke = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(colorHierarchy.accent, rng, 6, 0.05), bgLum);
|
|
4160
|
+
// Get best style for this hero shape
|
|
4161
|
+
const heroProfile = (0, $24064302523652b1$export$4343b39fe47bd82c)[heroShape];
|
|
4162
|
+
const heroStyle = heroProfile ? heroProfile.bestStyles[Math.floor(rng() * heroProfile.bestStyles.length)] : rng() < 0.4 ? "watercolor" : "fill-and-stroke";
|
|
2205
4163
|
ctx.globalAlpha = 0.5 + rng() * 0.2;
|
|
2206
4164
|
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, heroShape, heroFocal.x, heroFocal.y, {
|
|
2207
4165
|
fillColor: heroFill,
|
|
@@ -2212,14 +4170,20 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2212
4170
|
proportionType: "GOLDEN_RATIO",
|
|
2213
4171
|
glowRadius: (12 + rng() * 20) * scaleFactor,
|
|
2214
4172
|
glowColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
2215
|
-
gradientFillEnd: (0, $9d614e7d77fc2947$export$
|
|
2216
|
-
renderStyle:
|
|
4173
|
+
gradientFillEnd: (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
4174
|
+
renderStyle: heroStyle,
|
|
2217
4175
|
rng: rng
|
|
2218
4176
|
});
|
|
2219
|
-
|
|
4177
|
+
heroCenter = {
|
|
2220
4178
|
x: heroFocal.x,
|
|
2221
4179
|
y: heroFocal.y,
|
|
2222
4180
|
size: heroSize
|
|
4181
|
+
};
|
|
4182
|
+
shapePositions.push({
|
|
4183
|
+
x: heroFocal.x,
|
|
4184
|
+
y: heroFocal.y,
|
|
4185
|
+
size: heroSize,
|
|
4186
|
+
shape: heroShape
|
|
2223
4187
|
});
|
|
2224
4188
|
}
|
|
2225
4189
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
@@ -2230,46 +4194,64 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2230
4194
|
const numShapes = shapesPerLayer + Math.floor(rng() * shapesPerLayer * 0.3);
|
|
2231
4195
|
const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);
|
|
2232
4196
|
const layerSizeScale = 1 - layer * 0.15;
|
|
2233
|
-
//
|
|
4197
|
+
// Per-layer blend mode
|
|
2234
4198
|
const layerBlend = (0, $9beb8f41637c29fd$export$7bb7bff4e26fa06b)(rng);
|
|
2235
4199
|
ctx.globalCompositeOperation = layerBlend;
|
|
2236
|
-
//
|
|
4200
|
+
// Per-layer render style bias — prefer archetype styles
|
|
2237
4201
|
const layerRenderStyle = rng() < 0.6 ? archetype.preferredStyles[Math.floor(rng() * archetype.preferredStyles.length)] : (0, $9beb8f41637c29fd$export$9fd4e64b2acd410e)(rng);
|
|
2238
|
-
//
|
|
2239
|
-
const atmosphericDesat = layerRatio * 0.3;
|
|
4202
|
+
// Atmospheric desaturation for later layers
|
|
4203
|
+
const atmosphericDesat = layerRatio * 0.3;
|
|
4204
|
+
// Depth-of-field simulation — later layers are "further away"
|
|
4205
|
+
// Reduce stroke widths and shift colors toward the background
|
|
4206
|
+
const dofFactor = 1 - layerRatio * 0.5; // 1.0 for front layer, 0.5 for back
|
|
4207
|
+
const dofStrokeScale = 0.4 + dofFactor * 0.6; // strokes thin out with depth
|
|
4208
|
+
const dofContrastReduction = layerRatio * 0.2; // colors fade toward bg
|
|
2240
4209
|
for(let i = 0; i < numShapes; i++){
|
|
2241
4210
|
// Position from composition mode, then focal bias
|
|
2242
4211
|
const rawPos = $b623126c6e9cbb71$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
2243
4212
|
const [x, y] = applyFocalBias(rawPos.x, rawPos.y);
|
|
2244
|
-
//
|
|
4213
|
+
// Skip shapes in void zones, reduce in dense areas
|
|
2245
4214
|
if ($b623126c6e9cbb71$var$isInVoidZone(x, y, voidZones)) {
|
|
2246
|
-
// 85% chance to skip — allows a few shapes to bleed in
|
|
2247
4215
|
if (rng() < 0.85) continue;
|
|
2248
4216
|
}
|
|
2249
4217
|
if ($b623126c6e9cbb71$var$localDensity(x, y, shapePositions, densityCheckRadius) > maxLocalDensity) {
|
|
2250
|
-
if (rng() < 0.6) continue;
|
|
4218
|
+
if (rng() < 0.6) continue;
|
|
2251
4219
|
}
|
|
2252
|
-
// Weighted shape selection
|
|
2253
|
-
const shape = $b623126c6e9cbb71$var$pickShape(rng, layerRatio, shapeNames);
|
|
2254
4220
|
// Power distribution for size — archetype controls the curve
|
|
2255
4221
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
2256
4222
|
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
4223
|
+
// Size fraction for affinity-aware shape selection
|
|
4224
|
+
const sizeFraction = size / adjustedMaxSize;
|
|
4225
|
+
// Palette-driven shape selection (replaces naive pickShape)
|
|
4226
|
+
const shape = (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, sizeFraction);
|
|
2257
4227
|
// Flow-field rotation in flow-field mode, random otherwise
|
|
2258
|
-
|
|
2259
|
-
//
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
4228
|
+
let rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
|
|
4229
|
+
// Hero avoidance: shapes near the hero orient toward it
|
|
4230
|
+
if (heroCenter) {
|
|
4231
|
+
const distToHero = Math.hypot(x - heroCenter.x, y - heroCenter.y);
|
|
4232
|
+
const heroInfluence = heroCenter.size * 1.5;
|
|
4233
|
+
if (distToHero < heroInfluence && distToHero > 0) {
|
|
4234
|
+
const angleToHero = Math.atan2(heroCenter.y - y, heroCenter.x - x) * 180 / Math.PI;
|
|
4235
|
+
const blendFactor = 1 - distToHero / heroInfluence;
|
|
4236
|
+
rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
// Positional color from hierarchy + jitter
|
|
4240
|
+
let fillBase = $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, colorHierarchy, rng);
|
|
4241
|
+
const strokeBase = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
4242
|
+
// Desaturate colors on later layers for depth
|
|
2263
4243
|
if (atmosphericDesat > 0) fillBase = (0, $9d614e7d77fc2947$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
2264
4244
|
// Temperature contrast: shift foreground shapes opposite to background
|
|
2265
4245
|
if (fgTempTarget) fillBase = (0, $9d614e7d77fc2947$export$51ea55f869b7e0d3)(fillBase, fgTempTarget, 0.15 + layerRatio * 0.1);
|
|
2266
|
-
const fillColor = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$
|
|
2267
|
-
const strokeColor = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$
|
|
4246
|
+
const fillColor = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(fillBase, rng, 6, 0.05), bgLum);
|
|
4247
|
+
const strokeColor = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
|
|
2268
4248
|
// Semi-transparent fill
|
|
2269
4249
|
const fillAlpha = 0.2 + rng() * 0.5;
|
|
2270
4250
|
const transparentFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, fillAlpha);
|
|
2271
|
-
const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor;
|
|
2272
|
-
|
|
4251
|
+
const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor * dofStrokeScale;
|
|
4252
|
+
// Depth-of-field: reduce opacity slightly for distant layers
|
|
4253
|
+
const dofOpacityScale = 1 - dofContrastReduction;
|
|
4254
|
+
ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5) * dofOpacityScale;
|
|
2273
4255
|
// Glow on sacred shapes more often — scaled by archetype
|
|
2274
4256
|
const isSacred = $b623126c6e9cbb71$var$SACRED_SHAPES.includes(shape);
|
|
2275
4257
|
const baseGlowChance = isSacred ? 0.45 : 0.2;
|
|
@@ -2278,58 +4260,177 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2278
4260
|
const glowRadius = hasGlow ? (8 + rng() * 20) * scaleFactor : 0;
|
|
2279
4261
|
// Gradient fill on ~30%
|
|
2280
4262
|
const hasGradient = rng() < 0.3;
|
|
2281
|
-
const gradientEnd = hasGradient ? (0, $9d614e7d77fc2947$export$
|
|
2282
|
-
//
|
|
2283
|
-
const shapeRenderStyle =
|
|
2284
|
-
//
|
|
4263
|
+
const gradientEnd = hasGradient ? (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1) : undefined;
|
|
4264
|
+
// Affinity-aware render style selection
|
|
4265
|
+
const shapeRenderStyle = (0, $24064302523652b1$export$ab873bb6fb56c1a8)(shape, layerRenderStyle, rng);
|
|
4266
|
+
// Organic edge jitter — applied via watercolor style on ~15% of shapes
|
|
2285
4267
|
const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
|
|
2286
4268
|
const finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
|
|
2287
|
-
|
|
4269
|
+
// Consistent light direction — subtle shadow offset
|
|
4270
|
+
const shadowDist = hasGlow ? 0 : size * 0.02;
|
|
4271
|
+
const shadowOffX = shadowDist * Math.cos(lightAngle);
|
|
4272
|
+
const shadowOffY = shadowDist * Math.sin(lightAngle);
|
|
4273
|
+
// ── 5a. Tangent placement — nudge toward nearest shape edge ──
|
|
4274
|
+
let finalX = x;
|
|
4275
|
+
let finalY = y;
|
|
4276
|
+
if (shapePositions.length > 0 && rng() < 0.25) {
|
|
4277
|
+
// Find nearest placed shape
|
|
4278
|
+
let nearestDist = Infinity;
|
|
4279
|
+
let nearestPos = null;
|
|
4280
|
+
for (const sp of shapePositions){
|
|
4281
|
+
const d = Math.hypot(x - sp.x, y - sp.y);
|
|
4282
|
+
if (d < nearestDist && d > 0) {
|
|
4283
|
+
nearestDist = d;
|
|
4284
|
+
nearestPos = sp;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (nearestPos) {
|
|
4288
|
+
// Target distance: edges kissing (sum of half-sizes)
|
|
4289
|
+
const targetDist = (size + nearestPos.size) * 0.5;
|
|
4290
|
+
if (nearestDist > targetDist * 0.5 && nearestDist < targetDist * 3) {
|
|
4291
|
+
const angle = Math.atan2(y - nearestPos.y, x - nearestPos.x);
|
|
4292
|
+
finalX = nearestPos.x + Math.cos(angle) * targetDist;
|
|
4293
|
+
finalY = nearestPos.y + Math.sin(angle) * targetDist;
|
|
4294
|
+
// Keep in bounds
|
|
4295
|
+
finalX = Math.max(0, Math.min(width, finalX));
|
|
4296
|
+
finalY = Math.max(0, Math.min(height, finalY));
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
// ── 5b. Shape mirroring — basic shapes get reflected copies ──
|
|
4301
|
+
const mirrorAxis = (0, $9beb8f41637c29fd$export$879206e23912d1a9)(rng);
|
|
4302
|
+
const isBasicShape = [
|
|
4303
|
+
"circle",
|
|
4304
|
+
"triangle",
|
|
4305
|
+
"square",
|
|
4306
|
+
"hexagon",
|
|
4307
|
+
"star",
|
|
4308
|
+
"diamond",
|
|
4309
|
+
"crescent",
|
|
4310
|
+
"penroseTile",
|
|
4311
|
+
"reuleauxTriangle"
|
|
4312
|
+
].includes(shape);
|
|
4313
|
+
const shouldMirror = mirrorAxis !== null && isBasicShape && size > adjustedMaxSize * 0.2;
|
|
4314
|
+
const shapeConfig = {
|
|
2288
4315
|
fillColor: transparentFill,
|
|
2289
4316
|
strokeColor: strokeColor,
|
|
2290
4317
|
strokeWidth: strokeWidth,
|
|
2291
4318
|
size: size,
|
|
2292
4319
|
rotation: rotation,
|
|
2293
4320
|
proportionType: "GOLDEN_RATIO",
|
|
2294
|
-
glowRadius: glowRadius,
|
|
2295
|
-
glowColor: hasGlow ? (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.6) : undefined,
|
|
4321
|
+
glowRadius: glowRadius || (shadowDist > 0 ? shadowDist * 2 : 0),
|
|
4322
|
+
glowColor: hasGlow ? (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
2296
4323
|
gradientFillEnd: gradientEnd,
|
|
2297
4324
|
renderStyle: finalRenderStyle,
|
|
2298
4325
|
rng: rng
|
|
4326
|
+
};
|
|
4327
|
+
if (shouldMirror) (0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4328
|
+
...shapeConfig,
|
|
4329
|
+
mirrorAxis: mirrorAxis,
|
|
4330
|
+
mirrorGap: size * (0.1 + rng() * 0.3)
|
|
2299
4331
|
});
|
|
4332
|
+
else (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
|
|
2300
4333
|
shapePositions.push({
|
|
2301
|
-
x:
|
|
2302
|
-
y:
|
|
2303
|
-
size: size
|
|
4334
|
+
x: finalX,
|
|
4335
|
+
y: finalY,
|
|
4336
|
+
size: size,
|
|
4337
|
+
shape: shape
|
|
2304
4338
|
});
|
|
2305
|
-
// ──
|
|
4339
|
+
// ── 5c. Size echo — large shapes spawn trailing smaller copies ──
|
|
4340
|
+
if (size > adjustedMaxSize * 0.5 && rng() < 0.2) {
|
|
4341
|
+
const echoCount = 2 + Math.floor(rng() * 2);
|
|
4342
|
+
const echoAngle = rng() * Math.PI * 2;
|
|
4343
|
+
for(let e = 0; e < echoCount; e++){
|
|
4344
|
+
const echoScale = 0.3 - e * 0.08;
|
|
4345
|
+
const echoDist = size * (0.6 + e * 0.4);
|
|
4346
|
+
const echoX = finalX + Math.cos(echoAngle) * echoDist;
|
|
4347
|
+
const echoY = finalY + Math.sin(echoAngle) * echoDist;
|
|
4348
|
+
const echoSize = size * Math.max(0.1, echoScale);
|
|
4349
|
+
if (echoX < 0 || echoX > width || echoY < 0 || echoY > height) continue;
|
|
4350
|
+
ctx.globalAlpha = layerOpacity * (0.4 - e * 0.1);
|
|
4351
|
+
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, echoX, echoY, {
|
|
4352
|
+
fillColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, fillAlpha * 0.6),
|
|
4353
|
+
strokeColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(strokeColor, 0.4),
|
|
4354
|
+
strokeWidth: strokeWidth * 0.6,
|
|
4355
|
+
size: echoSize,
|
|
4356
|
+
rotation: rotation + (e + 1) * 15,
|
|
4357
|
+
proportionType: "GOLDEN_RATIO",
|
|
4358
|
+
renderStyle: finalRenderStyle,
|
|
4359
|
+
rng: rng
|
|
4360
|
+
});
|
|
4361
|
+
shapePositions.push({
|
|
4362
|
+
x: echoX,
|
|
4363
|
+
y: echoY,
|
|
4364
|
+
size: echoSize,
|
|
4365
|
+
shape: shape
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
// ── 5d. Recursive nesting ──────────────────────────────────
|
|
2306
4370
|
if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
|
|
2307
4371
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
2308
4372
|
for(let n = 0; n < innerCount; n++){
|
|
2309
|
-
|
|
4373
|
+
// Pick inner shape from palette affinities
|
|
4374
|
+
const innerSizeFraction = size * 0.25 / adjustedMaxSize;
|
|
4375
|
+
const innerShape = (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, innerSizeFraction);
|
|
2310
4376
|
const innerSize = size * (0.15 + rng() * 0.25);
|
|
2311
4377
|
const innerOffX = (rng() - 0.5) * size * 0.4;
|
|
2312
4378
|
const innerOffY = (rng() - 0.5) * size * 0.4;
|
|
2313
4379
|
const innerRot = rng() * 360;
|
|
2314
|
-
const innerFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$
|
|
4380
|
+
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);
|
|
2315
4381
|
ctx.globalAlpha = layerOpacity * 0.7;
|
|
2316
|
-
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, innerShape,
|
|
4382
|
+
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, innerShape, finalX + innerOffX, finalY + innerOffY, {
|
|
2317
4383
|
fillColor: innerFill,
|
|
2318
4384
|
strokeColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(strokeColor, 0.5),
|
|
2319
4385
|
strokeWidth: strokeWidth * 0.6,
|
|
2320
4386
|
size: innerSize,
|
|
2321
4387
|
rotation: innerRot,
|
|
2322
4388
|
proportionType: "GOLDEN_RATIO",
|
|
2323
|
-
renderStyle:
|
|
4389
|
+
renderStyle: (0, $24064302523652b1$export$ab873bb6fb56c1a8)(innerShape, layerRenderStyle, rng),
|
|
4390
|
+
rng: rng
|
|
4391
|
+
});
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
// ── 5e. Shape constellations — pre-composed groups ─────────
|
|
4395
|
+
if (size > adjustedMaxSize * 0.35 && rng() < 0.12) {
|
|
4396
|
+
const constellation = $b623126c6e9cbb71$var$CONSTELLATIONS[Math.floor(rng() * $b623126c6e9cbb71$var$CONSTELLATIONS.length)];
|
|
4397
|
+
const members = constellation.build(rng, size);
|
|
4398
|
+
const groupRotation = rng() * Math.PI * 2;
|
|
4399
|
+
const cosR = Math.cos(groupRotation);
|
|
4400
|
+
const sinR = Math.sin(groupRotation);
|
|
4401
|
+
for (const member of members){
|
|
4402
|
+
// Rotate the group offset by the group rotation
|
|
4403
|
+
const mx = finalX + member.dx * cosR - member.dy * sinR;
|
|
4404
|
+
const my = finalY + member.dx * sinR + member.dy * cosR;
|
|
4405
|
+
if (mx < 0 || mx > width || my < 0 || my > height) continue;
|
|
4406
|
+
const memberFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 8, 0.06), fillAlpha * 0.8);
|
|
4407
|
+
const memberStroke = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
|
|
4408
|
+
ctx.globalAlpha = layerOpacity * 0.6;
|
|
4409
|
+
// Use the member's shape if available, otherwise fall back to palette
|
|
4410
|
+
const memberShape = shapeNames.includes(member.shape) ? member.shape : (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, member.size / adjustedMaxSize);
|
|
4411
|
+
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, memberShape, mx, my, {
|
|
4412
|
+
fillColor: memberFill,
|
|
4413
|
+
strokeColor: memberStroke,
|
|
4414
|
+
strokeWidth: strokeWidth * 0.7,
|
|
4415
|
+
size: member.size,
|
|
4416
|
+
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
4417
|
+
proportionType: "GOLDEN_RATIO",
|
|
4418
|
+
renderStyle: (0, $24064302523652b1$export$ab873bb6fb56c1a8)(memberShape, layerRenderStyle, rng),
|
|
2324
4419
|
rng: rng
|
|
2325
4420
|
});
|
|
4421
|
+
shapePositions.push({
|
|
4422
|
+
x: mx,
|
|
4423
|
+
y: my,
|
|
4424
|
+
size: member.size,
|
|
4425
|
+
shape: memberShape
|
|
4426
|
+
});
|
|
2326
4427
|
}
|
|
2327
4428
|
}
|
|
2328
4429
|
}
|
|
2329
4430
|
}
|
|
2330
4431
|
// Reset blend mode for post-processing passes
|
|
2331
4432
|
ctx.globalCompositeOperation = "source-over";
|
|
2332
|
-
// ── 6. Flow-line pass
|
|
4433
|
+
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
2333
4434
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
2334
4435
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
2335
4436
|
for(let i = 0; i < numFlowLines; i++){
|
|
@@ -2338,9 +4439,13 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2338
4439
|
const steps = 30 + Math.floor(rng() * 40);
|
|
2339
4440
|
const stepLen = (3 + rng() * 5) * scaleFactor;
|
|
2340
4441
|
const startWidth = (1 + rng() * 3) * scaleFactor;
|
|
2341
|
-
|
|
4442
|
+
// Variable color: interpolate between two hierarchy colors along the stroke
|
|
4443
|
+
const lineColorStart = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
|
|
4444
|
+
const lineColorEnd = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
|
|
2342
4445
|
const lineAlpha = 0.06 + rng() * 0.1;
|
|
2343
|
-
//
|
|
4446
|
+
// Pressure simulation: sinusoidal width variation
|
|
4447
|
+
const pressureFreq = 2 + rng() * 4;
|
|
4448
|
+
const pressurePhase = rng() * Math.PI * 2;
|
|
2344
4449
|
let prevX = fx;
|
|
2345
4450
|
let prevY = fy;
|
|
2346
4451
|
for(let s = 0; s < steps; s++){
|
|
@@ -2348,37 +4453,61 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2348
4453
|
fx += Math.cos(angle) * stepLen;
|
|
2349
4454
|
fy += Math.sin(angle) * stepLen;
|
|
2350
4455
|
if (fx < 0 || fx > width || fy < 0 || fy > height) break;
|
|
2351
|
-
|
|
2352
|
-
|
|
4456
|
+
const t = s / steps;
|
|
4457
|
+
// Taper + pressure
|
|
4458
|
+
const taper = 1 - t * 0.8;
|
|
4459
|
+
const pressure = 0.6 + 0.4 * Math.sin(t * pressureFreq * Math.PI + pressurePhase);
|
|
2353
4460
|
ctx.globalAlpha = lineAlpha * taper;
|
|
4461
|
+
// Interpolate color along stroke
|
|
4462
|
+
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);
|
|
2354
4463
|
ctx.strokeStyle = lineColor;
|
|
2355
|
-
ctx.lineWidth = startWidth * taper;
|
|
4464
|
+
ctx.lineWidth = startWidth * taper * pressure;
|
|
2356
4465
|
ctx.lineCap = "round";
|
|
2357
4466
|
ctx.beginPath();
|
|
2358
4467
|
ctx.moveTo(prevX, prevY);
|
|
2359
4468
|
ctx.lineTo(fx, fy);
|
|
2360
4469
|
ctx.stroke();
|
|
4470
|
+
// Branching: ~12% chance per step to spawn a thinner child stroke
|
|
4471
|
+
if (rng() < 0.12 && s > 5 && s < steps - 10) {
|
|
4472
|
+
const branchAngle = angle + (rng() < 0.5 ? 1 : -1) * (0.3 + rng() * 0.5);
|
|
4473
|
+
let bx = fx;
|
|
4474
|
+
let by = fy;
|
|
4475
|
+
let bPrevX = fx;
|
|
4476
|
+
let bPrevY = fy;
|
|
4477
|
+
const branchSteps = 5 + Math.floor(rng() * 10);
|
|
4478
|
+
const branchWidth = startWidth * taper * 0.4;
|
|
4479
|
+
for(let bs = 0; bs < branchSteps; bs++){
|
|
4480
|
+
const bAngle = branchAngle + (rng() - 0.5) * 0.2;
|
|
4481
|
+
bx += Math.cos(bAngle) * stepLen * 0.8;
|
|
4482
|
+
by += Math.sin(bAngle) * stepLen * 0.8;
|
|
4483
|
+
if (bx < 0 || bx > width || by < 0 || by > height) break;
|
|
4484
|
+
const bTaper = 1 - bs / branchSteps * 0.9;
|
|
4485
|
+
ctx.globalAlpha = lineAlpha * taper * bTaper * 0.6;
|
|
4486
|
+
ctx.lineWidth = branchWidth * bTaper;
|
|
4487
|
+
ctx.beginPath();
|
|
4488
|
+
ctx.moveTo(bPrevX, bPrevY);
|
|
4489
|
+
ctx.lineTo(bx, by);
|
|
4490
|
+
ctx.stroke();
|
|
4491
|
+
bPrevX = bx;
|
|
4492
|
+
bPrevY = by;
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
2361
4495
|
prevX = fx;
|
|
2362
4496
|
prevY = fy;
|
|
2363
4497
|
}
|
|
2364
4498
|
}
|
|
2365
4499
|
// ── 6b. Apply symmetry mirroring ─────────────────────────────────
|
|
2366
|
-
// Mirror the rendered content (shapes + flow lines) before post-processing.
|
|
2367
|
-
// Uses ctx.canvas which is available in both Node (@napi-rs/canvas) and browsers.
|
|
2368
4500
|
if (symmetryMode !== "none") {
|
|
2369
4501
|
const canvas = ctx.canvas;
|
|
2370
4502
|
ctx.save();
|
|
2371
4503
|
if (symmetryMode === "bilateral-x" || symmetryMode === "quad") {
|
|
2372
|
-
// Mirror left half onto right half
|
|
2373
4504
|
ctx.save();
|
|
2374
4505
|
ctx.translate(width, 0);
|
|
2375
4506
|
ctx.scale(-1, 1);
|
|
2376
|
-
// Draw the left half (0 to cx) onto the mirrored right side
|
|
2377
4507
|
ctx.drawImage(canvas, 0, 0, Math.ceil(cx), height, 0, 0, Math.ceil(cx), height);
|
|
2378
4508
|
ctx.restore();
|
|
2379
4509
|
}
|
|
2380
4510
|
if (symmetryMode === "bilateral-y" || symmetryMode === "quad") {
|
|
2381
|
-
// Mirror top half onto bottom half
|
|
2382
4511
|
ctx.save();
|
|
2383
4512
|
ctx.translate(0, height);
|
|
2384
4513
|
ctx.scale(1, -1);
|
|
@@ -2401,7 +4530,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2401
4530
|
}
|
|
2402
4531
|
// ── 8. Vignette — darken edges to draw the eye inward ───────────
|
|
2403
4532
|
ctx.globalAlpha = 1;
|
|
2404
|
-
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
4533
|
+
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
2405
4534
|
const vigGrad = ctx.createRadialGradient(cx, cy, Math.min(width, height) * 0.3, cx, cy, bgRadius);
|
|
2406
4535
|
vigGrad.addColorStop(0, "rgba(0,0,0,0)");
|
|
2407
4536
|
vigGrad.addColorStop(0.6, "rgba(0,0,0,0)");
|
|
@@ -2427,13 +4556,57 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2427
4556
|
const cpx = mx + -dy / (dist || 1) * bulge;
|
|
2428
4557
|
const cpy = my + dx / (dist || 1) * bulge;
|
|
2429
4558
|
ctx.globalAlpha = 0.06 + rng() * 0.1;
|
|
2430
|
-
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)(
|
|
4559
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
2431
4560
|
ctx.beginPath();
|
|
2432
4561
|
ctx.moveTo(a.x, a.y);
|
|
2433
4562
|
ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);
|
|
2434
4563
|
ctx.stroke();
|
|
2435
4564
|
}
|
|
2436
4565
|
}
|
|
4566
|
+
// ── 10. Post-processing ────────────────────────────────────────
|
|
4567
|
+
// 10a. Color grading — unified tone across the whole image
|
|
4568
|
+
// Apply as a semi-transparent overlay in the grade hue
|
|
4569
|
+
ctx.globalAlpha = colorGrade.intensity * 0.25;
|
|
4570
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
4571
|
+
const gradeHsl = `hsl(${Math.round(colorGrade.hue)}, 40%, 50%)`;
|
|
4572
|
+
ctx.fillStyle = gradeHsl;
|
|
4573
|
+
ctx.fillRect(0, 0, width, height);
|
|
4574
|
+
ctx.globalCompositeOperation = "source-over";
|
|
4575
|
+
// 10b. Chromatic aberration — subtle RGB channel offset at edges
|
|
4576
|
+
// Only apply for neon/cosmic/ethereal archetypes where it fits
|
|
4577
|
+
const chromaArchetypes = [
|
|
4578
|
+
"neon-glow",
|
|
4579
|
+
"cosmic",
|
|
4580
|
+
"ethereal"
|
|
4581
|
+
];
|
|
4582
|
+
if (chromaArchetypes.includes(archetype.name)) {
|
|
4583
|
+
const chromaOffset = Math.ceil(2 * scaleFactor);
|
|
4584
|
+
const canvas = ctx.canvas;
|
|
4585
|
+
// Shift red channel slightly
|
|
4586
|
+
ctx.globalAlpha = 0.03;
|
|
4587
|
+
ctx.globalCompositeOperation = "screen";
|
|
4588
|
+
ctx.drawImage(canvas, chromaOffset, 0, width, height, 0, 0, width, height);
|
|
4589
|
+
// Shift blue channel opposite
|
|
4590
|
+
ctx.drawImage(canvas, -chromaOffset, 0, width, height, 0, 0, width, height);
|
|
4591
|
+
ctx.globalCompositeOperation = "source-over";
|
|
4592
|
+
}
|
|
4593
|
+
// 10c. Bloom — soft glow on bright areas for neon/cosmic archetypes
|
|
4594
|
+
const bloomArchetypes = [
|
|
4595
|
+
"neon-glow",
|
|
4596
|
+
"cosmic"
|
|
4597
|
+
];
|
|
4598
|
+
if (bloomArchetypes.includes(archetype.name)) {
|
|
4599
|
+
const canvas = ctx.canvas;
|
|
4600
|
+
ctx.globalAlpha = 0.08;
|
|
4601
|
+
ctx.globalCompositeOperation = "screen";
|
|
4602
|
+
// Draw the image slightly scaled up and blurred via shadow
|
|
4603
|
+
ctx.save();
|
|
4604
|
+
ctx.shadowBlur = 30 * scaleFactor;
|
|
4605
|
+
ctx.shadowColor = "rgba(255,255,255,0.3)";
|
|
4606
|
+
ctx.drawImage(canvas, 0, 0, width, height);
|
|
4607
|
+
ctx.restore();
|
|
4608
|
+
ctx.globalCompositeOperation = "source-over";
|
|
4609
|
+
}
|
|
2437
4610
|
ctx.globalAlpha = 1;
|
|
2438
4611
|
}
|
|
2439
4612
|
|