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