git-hash-art 0.8.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 +10 -0
- package/bin/cli.js +17 -14
- package/bin/generateVersionComparison.js +353 -0
- package/dist/browser.js +1246 -36
- package/dist/browser.js.map +1 -1
- package/dist/main.js +1246 -36
- package/dist/main.js.map +1 -1
- package/dist/module.js +1246 -36
- package/dist/module.js.map +1 -1
- package/package.json +2 -1
- package/src/lib/archetypes.ts +68 -0
- package/src/lib/canvas/draw.ts +318 -1
- package/src/lib/canvas/shapes/affinity.ts +146 -1
- package/src/lib/canvas/shapes/procedural.ts +395 -32
- package/src/lib/render.ts +259 -13
package/dist/module.js
CHANGED
|
@@ -1317,35 +1317,32 @@ const $d63629e16208c310$export$c2fc138f94dd4b2a = {
|
|
|
1317
1317
|
*/ const $a7241acce45d5513$export$580f80cfb9de73bc = (ctx, size, config)=>{
|
|
1318
1318
|
const rng = config?.rng ?? Math.random;
|
|
1319
1319
|
const r = size / 2;
|
|
1320
|
-
const numPoints = 5 + Math.floor(rng() * 5);
|
|
1320
|
+
const numPoints = 5 + Math.floor(rng() * 5);
|
|
1321
1321
|
const points = [];
|
|
1322
1322
|
for(let i = 0; i < numPoints; i++){
|
|
1323
1323
|
const angle = i / numPoints * Math.PI * 2;
|
|
1324
|
-
const jitter = 0.5 + rng() * 0.5;
|
|
1324
|
+
const jitter = 0.5 + rng() * 0.5;
|
|
1325
1325
|
points.push({
|
|
1326
1326
|
x: Math.cos(angle) * r * jitter,
|
|
1327
1327
|
y: Math.sin(angle) * r * jitter
|
|
1328
1328
|
});
|
|
1329
1329
|
}
|
|
1330
1330
|
ctx.beginPath();
|
|
1331
|
-
// Start at midpoint between last and first point
|
|
1332
1331
|
const last = points[points.length - 1];
|
|
1333
1332
|
const first = points[0];
|
|
1334
1333
|
ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
|
|
1335
1334
|
for(let i = 0; i < numPoints; i++){
|
|
1336
1335
|
const curr = points[i];
|
|
1337
1336
|
const next = points[(i + 1) % numPoints];
|
|
1338
|
-
|
|
1339
|
-
const midY = (curr.y + next.y) / 2;
|
|
1340
|
-
ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
|
|
1337
|
+
ctx.quadraticCurveTo(curr.x, curr.y, (curr.x + next.x) / 2, (curr.y + next.y) / 2);
|
|
1341
1338
|
}
|
|
1342
1339
|
ctx.closePath();
|
|
1343
1340
|
};
|
|
1344
1341
|
const $a7241acce45d5513$export$7a6094023f0902a6 = (ctx, size, config)=>{
|
|
1345
1342
|
const rng = config?.rng ?? Math.random;
|
|
1346
1343
|
const r = size / 2;
|
|
1347
|
-
const sides = 3 + Math.floor(rng() * 10);
|
|
1348
|
-
const jitterAmount = 0.1 + rng() * 0.4;
|
|
1344
|
+
const sides = 3 + Math.floor(rng() * 10);
|
|
1345
|
+
const jitterAmount = 0.1 + rng() * 0.4;
|
|
1349
1346
|
ctx.beginPath();
|
|
1350
1347
|
for(let i = 0; i < sides; i++){
|
|
1351
1348
|
const angle = i / sides * Math.PI * 2 - Math.PI / 2;
|
|
@@ -1360,10 +1357,9 @@ const $a7241acce45d5513$export$7a6094023f0902a6 = (ctx, size, config)=>{
|
|
|
1360
1357
|
const $a7241acce45d5513$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
|
|
1361
1358
|
const rng = config?.rng ?? Math.random;
|
|
1362
1359
|
const r = size / 2;
|
|
1363
|
-
|
|
1364
|
-
const
|
|
1365
|
-
const
|
|
1366
|
-
const phase = rng() * Math.PI; // phase offset
|
|
1360
|
+
const freqA = 1 + Math.floor(rng() * 5);
|
|
1361
|
+
const freqB = 1 + Math.floor(rng() * 5);
|
|
1362
|
+
const phase = rng() * Math.PI;
|
|
1367
1363
|
const steps = 120;
|
|
1368
1364
|
ctx.beginPath();
|
|
1369
1365
|
for(let i = 0; i <= steps; i++){
|
|
@@ -1378,7 +1374,6 @@ const $a7241acce45d5513$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
|
|
|
1378
1374
|
const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
1379
1375
|
const rng = config?.rng ?? Math.random;
|
|
1380
1376
|
const r = size / 2;
|
|
1381
|
-
// Exponent range: 0.3 (spiky astroid) to 5 (rounded rectangle)
|
|
1382
1377
|
const n = 0.3 + rng() * 4.7;
|
|
1383
1378
|
const steps = 120;
|
|
1384
1379
|
ctx.beginPath();
|
|
@@ -1386,7 +1381,6 @@ const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
|
1386
1381
|
const t = i / steps * Math.PI * 2;
|
|
1387
1382
|
const cosT = Math.cos(t);
|
|
1388
1383
|
const sinT = Math.sin(t);
|
|
1389
|
-
// Superellipse parametric form
|
|
1390
1384
|
const x = Math.sign(cosT) * Math.pow(Math.abs(cosT), 2 / n) * r;
|
|
1391
1385
|
const y = Math.sign(sinT) * Math.pow(Math.abs(sinT), 2 / n) * r;
|
|
1392
1386
|
if (i === 0) ctx.moveTo(x, y);
|
|
@@ -1397,11 +1391,9 @@ const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
|
|
|
1397
1391
|
const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
1398
1392
|
const rng = config?.rng ?? Math.random;
|
|
1399
1393
|
const scale = size / 2;
|
|
1400
|
-
// R = outer radius, r = inner radius, d = pen distance from inner center
|
|
1401
1394
|
const R = 1;
|
|
1402
|
-
const r = 0.2 + rng() * 0.6;
|
|
1403
|
-
const d = 0.3 + rng() * 0.7;
|
|
1404
|
-
// Number of full rotations needed to close the curve
|
|
1395
|
+
const r = 0.2 + rng() * 0.6;
|
|
1396
|
+
const d = 0.3 + rng() * 0.7;
|
|
1405
1397
|
const gcd = (a, b)=>{
|
|
1406
1398
|
const ai = Math.round(a * 1000);
|
|
1407
1399
|
const bi = Math.round(b * 1000);
|
|
@@ -1409,7 +1401,7 @@ const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
|
1409
1401
|
return g(ai, bi) / 1000;
|
|
1410
1402
|
};
|
|
1411
1403
|
const period = r / gcd(R, r);
|
|
1412
|
-
const maxT = Math.min(period, 10) * Math.PI * 2;
|
|
1404
|
+
const maxT = Math.min(period, 10) * Math.PI * 2;
|
|
1413
1405
|
const steps = Math.min(600, Math.floor(maxT * 20));
|
|
1414
1406
|
ctx.beginPath();
|
|
1415
1407
|
for(let i = 0; i <= steps; i++){
|
|
@@ -1424,9 +1416,9 @@ const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
|
|
|
1424
1416
|
const $a7241acce45d5513$export$7608ccd03bfb705d = (ctx, size, config)=>{
|
|
1425
1417
|
const rng = config?.rng ?? Math.random;
|
|
1426
1418
|
const r = size / 2;
|
|
1427
|
-
const rings = 2 + Math.floor(rng() * 4);
|
|
1428
|
-
const freq = 3 + Math.floor(rng() * 12);
|
|
1429
|
-
const amp = 0.05 + rng() * 0.15;
|
|
1419
|
+
const rings = 2 + Math.floor(rng() * 4);
|
|
1420
|
+
const freq = 3 + Math.floor(rng() * 12);
|
|
1421
|
+
const amp = 0.05 + rng() * 0.15;
|
|
1430
1422
|
ctx.beginPath();
|
|
1431
1423
|
for(let ring = 0; ring < rings; ring++){
|
|
1432
1424
|
const baseR = r * (0.3 + ring / rings * 0.7);
|
|
@@ -1444,7 +1436,7 @@ const $a7241acce45d5513$export$7608ccd03bfb705d = (ctx, size, config)=>{
|
|
|
1444
1436
|
const $a7241acce45d5513$export$11a377e7498bb523 = (ctx, size, config)=>{
|
|
1445
1437
|
const rng = config?.rng ?? Math.random;
|
|
1446
1438
|
const r = size / 2;
|
|
1447
|
-
const k = 2 + Math.floor(rng() * 6);
|
|
1439
|
+
const k = 2 + Math.floor(rng() * 6);
|
|
1448
1440
|
const steps = 200;
|
|
1449
1441
|
ctx.beginPath();
|
|
1450
1442
|
for(let i = 0; i <= steps; i++){
|
|
@@ -1457,6 +1449,308 @@ const $a7241acce45d5513$export$11a377e7498bb523 = (ctx, size, config)=>{
|
|
|
1457
1449
|
}
|
|
1458
1450
|
ctx.closePath();
|
|
1459
1451
|
};
|
|
1452
|
+
const $a7241acce45d5513$export$76b6526575ea179b = (ctx, size, config)=>{
|
|
1453
|
+
const rng = config?.rng ?? Math.random;
|
|
1454
|
+
const r = size / 2;
|
|
1455
|
+
const shardCount = 4 + Math.floor(rng() * 5); // 4-8 shards
|
|
1456
|
+
ctx.beginPath();
|
|
1457
|
+
for(let s = 0; s < shardCount; s++){
|
|
1458
|
+
const baseAngle = s / shardCount * Math.PI * 2 + (rng() - 0.5) * 0.3;
|
|
1459
|
+
const dist = r * (0.15 + rng() * 0.35);
|
|
1460
|
+
const cx = Math.cos(baseAngle) * dist;
|
|
1461
|
+
const cy = Math.sin(baseAngle) * dist;
|
|
1462
|
+
const shardSize = r * (0.2 + rng() * 0.4);
|
|
1463
|
+
const verts = 3 + Math.floor(rng() * 3); // 3-5 vertices per shard
|
|
1464
|
+
const shardAngleOffset = rng() * Math.PI * 2;
|
|
1465
|
+
for(let v = 0; v < verts; v++){
|
|
1466
|
+
const angle = shardAngleOffset + v / verts * Math.PI * 2;
|
|
1467
|
+
// Elongate shards along their radial direction
|
|
1468
|
+
const stretch = v % 2 === 0 ? 1.0 : 0.3 + rng() * 0.4;
|
|
1469
|
+
const px = cx + Math.cos(angle) * shardSize * stretch;
|
|
1470
|
+
const py = cy + Math.sin(angle) * shardSize * stretch;
|
|
1471
|
+
if (v === 0) ctx.moveTo(px, py);
|
|
1472
|
+
else ctx.lineTo(px, py);
|
|
1473
|
+
}
|
|
1474
|
+
ctx.closePath();
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
const $a7241acce45d5513$export$ed9ff98da5b05073 = (ctx, size, config)=>{
|
|
1478
|
+
const rng = config?.rng ?? Math.random;
|
|
1479
|
+
const r = size / 2;
|
|
1480
|
+
const edgeCount = 5 + Math.floor(rng() * 4); // 5-8 edges
|
|
1481
|
+
const points = [];
|
|
1482
|
+
// Generate edge midpoints at varying distances
|
|
1483
|
+
for(let i = 0; i < edgeCount; i++){
|
|
1484
|
+
const angle = i / edgeCount * Math.PI * 2 + (rng() - 0.5) * 0.4;
|
|
1485
|
+
const dist = r * (0.6 + rng() * 0.4);
|
|
1486
|
+
points.push({
|
|
1487
|
+
angle: angle,
|
|
1488
|
+
x: Math.cos(angle) * dist,
|
|
1489
|
+
y: Math.sin(angle) * dist
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
// Sort by angle for proper winding
|
|
1493
|
+
points.sort((a, b)=>a.angle - b.angle);
|
|
1494
|
+
ctx.beginPath();
|
|
1495
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1496
|
+
for(let i = 1; i < points.length; i++)ctx.lineTo(points[i].x, points[i].y);
|
|
1497
|
+
ctx.closePath();
|
|
1498
|
+
};
|
|
1499
|
+
const $a7241acce45d5513$export$e0452d9a794fe7e5 = (ctx, size, config)=>{
|
|
1500
|
+
const rng = config?.rng ?? Math.random;
|
|
1501
|
+
const r = size / 2;
|
|
1502
|
+
const biteSize = 0.6 + rng() * 0.3; // 60-90% of radius
|
|
1503
|
+
const biteOffset = r * (0.3 + rng() * 0.4);
|
|
1504
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
1505
|
+
// Outer circle
|
|
1506
|
+
ctx.beginPath();
|
|
1507
|
+
ctx.arc(0, 0, r, 0, Math.PI * 2);
|
|
1508
|
+
// Subtract inner circle using even-odd rule
|
|
1509
|
+
const bx = Math.cos(biteAngle) * biteOffset;
|
|
1510
|
+
const by = Math.sin(biteAngle) * biteOffset;
|
|
1511
|
+
// Draw inner circle counter-clockwise for subtraction
|
|
1512
|
+
ctx.moveTo(bx + r * biteSize, by);
|
|
1513
|
+
ctx.arc(bx, by, r * biteSize, 0, Math.PI * 2, true);
|
|
1514
|
+
};
|
|
1515
|
+
const $a7241acce45d5513$export$38bfe5eb52137e01 = (ctx, size, config)=>{
|
|
1516
|
+
const rng = config?.rng ?? Math.random;
|
|
1517
|
+
const r = size / 2;
|
|
1518
|
+
const segments = 12 + Math.floor(rng() * 8);
|
|
1519
|
+
const startAngle = rng() * Math.PI * 2;
|
|
1520
|
+
const curvature = (rng() - 0.5) * 0.4;
|
|
1521
|
+
// Build spine points
|
|
1522
|
+
const spine = [];
|
|
1523
|
+
let angle = startAngle;
|
|
1524
|
+
let px = 0, py = 0;
|
|
1525
|
+
for(let i = 0; i <= segments; i++){
|
|
1526
|
+
spine.push({
|
|
1527
|
+
x: px,
|
|
1528
|
+
y: py
|
|
1529
|
+
});
|
|
1530
|
+
const stepLen = r / segments * (1.5 + rng() * 0.5);
|
|
1531
|
+
angle += curvature + (rng() - 0.5) * 0.6;
|
|
1532
|
+
px += Math.cos(angle) * stepLen;
|
|
1533
|
+
py += Math.sin(angle) * stepLen;
|
|
1534
|
+
}
|
|
1535
|
+
// Build tapered outline by offsetting perpendicular to spine
|
|
1536
|
+
ctx.beginPath();
|
|
1537
|
+
const leftSide = [];
|
|
1538
|
+
const rightSide = [];
|
|
1539
|
+
for(let i = 0; i < spine.length; i++){
|
|
1540
|
+
const t = i / (spine.length - 1);
|
|
1541
|
+
const width = r * 0.12 * (1 - t * 0.9); // taper from thick to thin
|
|
1542
|
+
const next = spine[Math.min(i + 1, spine.length - 1)];
|
|
1543
|
+
const dx = next.x - spine[i].x;
|
|
1544
|
+
const dy = next.y - spine[i].y;
|
|
1545
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1546
|
+
const nx = -dy / len;
|
|
1547
|
+
const ny = dx / len;
|
|
1548
|
+
leftSide.push({
|
|
1549
|
+
x: spine[i].x + nx * width,
|
|
1550
|
+
y: spine[i].y + ny * width
|
|
1551
|
+
});
|
|
1552
|
+
rightSide.push({
|
|
1553
|
+
x: spine[i].x - nx * width,
|
|
1554
|
+
y: spine[i].y - ny * width
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
ctx.moveTo(leftSide[0].x, leftSide[0].y);
|
|
1558
|
+
for(let i = 1; i < leftSide.length; i++)ctx.lineTo(leftSide[i].x, leftSide[i].y);
|
|
1559
|
+
for(let i = rightSide.length - 1; i >= 0; i--)ctx.lineTo(rightSide[i].x, rightSide[i].y);
|
|
1560
|
+
ctx.closePath();
|
|
1561
|
+
};
|
|
1562
|
+
const $a7241acce45d5513$export$105caa8cfd63c422 = (ctx, size, config)=>{
|
|
1563
|
+
const rng = config?.rng ?? Math.random;
|
|
1564
|
+
const r = size / 2;
|
|
1565
|
+
const lobeCount = 4 + Math.floor(rng() * 4); // 4-7 lobes
|
|
1566
|
+
const spineAngle = rng() * Math.PI * 2;
|
|
1567
|
+
const spineLen = r * 0.6;
|
|
1568
|
+
ctx.beginPath();
|
|
1569
|
+
for(let i = 0; i < lobeCount; i++){
|
|
1570
|
+
const t = i / (lobeCount - 1) - 0.5; // -0.5 to 0.5
|
|
1571
|
+
const sx = Math.cos(spineAngle) * spineLen * t;
|
|
1572
|
+
const sy = Math.sin(spineAngle) * spineLen * t;
|
|
1573
|
+
// Offset perpendicular for cloud shape
|
|
1574
|
+
const perpAngle = spineAngle + Math.PI / 2;
|
|
1575
|
+
const perpOff = (rng() - 0.3) * r * 0.3;
|
|
1576
|
+
const cx = sx + Math.cos(perpAngle) * perpOff;
|
|
1577
|
+
const cy = sy + Math.sin(perpAngle) * perpOff;
|
|
1578
|
+
const lobeR = r * (0.25 + rng() * 0.2);
|
|
1579
|
+
ctx.moveTo(cx + lobeR, cy);
|
|
1580
|
+
ctx.arc(cx, cy, lobeR, 0, Math.PI * 2);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
const $a7241acce45d5513$export$e181e5bd3c539569 = (ctx, size, config)=>{
|
|
1584
|
+
const rng = config?.rng ?? Math.random;
|
|
1585
|
+
const r = size / 2;
|
|
1586
|
+
const spikeCount = 8 + Math.floor(rng() * 8); // 8-15 spikes
|
|
1587
|
+
const points = [];
|
|
1588
|
+
for(let i = 0; i < spikeCount; i++){
|
|
1589
|
+
const angle = i / spikeCount * Math.PI * 2;
|
|
1590
|
+
const isSpike = i % 2 === 0;
|
|
1591
|
+
const dist = isSpike ? r * (0.5 + rng() * 0.5 // spikes reach 50-100% of radius
|
|
1592
|
+
) : r * (0.15 + rng() * 0.2); // valleys at 15-35%
|
|
1593
|
+
points.push({
|
|
1594
|
+
x: Math.cos(angle) * dist,
|
|
1595
|
+
y: Math.sin(angle) * dist
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
ctx.beginPath();
|
|
1599
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1600
|
+
for(let i = 0; i < points.length; i++){
|
|
1601
|
+
const curr = points[i];
|
|
1602
|
+
const next = points[(i + 1) % points.length];
|
|
1603
|
+
const cpx = (curr.x + next.x) / 2 + (rng() - 0.5) * r * 0.15;
|
|
1604
|
+
const cpy = (curr.y + next.y) / 2 + (rng() - 0.5) * r * 0.15;
|
|
1605
|
+
ctx.quadraticCurveTo(cpx, cpy, next.x, next.y);
|
|
1606
|
+
}
|
|
1607
|
+
ctx.closePath();
|
|
1608
|
+
};
|
|
1609
|
+
const $a7241acce45d5513$export$155b4780b4c6bb7b = (ctx, size, config)=>{
|
|
1610
|
+
const rng = config?.rng ?? Math.random;
|
|
1611
|
+
const r = size / 2;
|
|
1612
|
+
const subdivisions = 1 + Math.floor(rng() * 3); // 1-3
|
|
1613
|
+
// Start with icosahedron vertices projected to 2D
|
|
1614
|
+
const baseVerts = 6 + subdivisions * 4;
|
|
1615
|
+
const points = [];
|
|
1616
|
+
for(let i = 0; i < baseVerts; i++){
|
|
1617
|
+
const angle = i / baseVerts * Math.PI * 2;
|
|
1618
|
+
const ring = i % 2 === 0 ? 1.0 : 0.5 + rng() * 0.3;
|
|
1619
|
+
points.push({
|
|
1620
|
+
x: Math.cos(angle) * r * ring,
|
|
1621
|
+
y: Math.sin(angle) * r * ring
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
ctx.beginPath();
|
|
1625
|
+
// Draw triangulated mesh — connect each point to neighbors and center
|
|
1626
|
+
for(let i = 0; i < points.length; i++){
|
|
1627
|
+
const next = points[(i + 1) % points.length];
|
|
1628
|
+
ctx.moveTo(points[i].x, points[i].y);
|
|
1629
|
+
ctx.lineTo(next.x, next.y);
|
|
1630
|
+
// Connect to center
|
|
1631
|
+
ctx.moveTo(points[i].x, points[i].y);
|
|
1632
|
+
ctx.lineTo(0, 0);
|
|
1633
|
+
// Cross-connect to create triangulation
|
|
1634
|
+
if (i % 2 === 0 && i + 2 < points.length) {
|
|
1635
|
+
ctx.moveTo(points[i].x, points[i].y);
|
|
1636
|
+
ctx.lineTo(points[i + 2].x, points[i + 2].y);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
// Outer ring
|
|
1640
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
1641
|
+
for(let i = 1; i < points.length; i++)ctx.lineTo(points[i].x, points[i].y);
|
|
1642
|
+
ctx.closePath();
|
|
1643
|
+
};
|
|
1644
|
+
const $a7241acce45d5513$export$9a7e648f11155172 = (ctx, size, config)=>{
|
|
1645
|
+
const rng = config?.rng ?? Math.random;
|
|
1646
|
+
const r = size / 2;
|
|
1647
|
+
const phi = (1 + Math.sqrt(5)) / 2; // golden ratio
|
|
1648
|
+
const isKite = rng() < 0.5;
|
|
1649
|
+
ctx.beginPath();
|
|
1650
|
+
if (isKite) {
|
|
1651
|
+
// Kite: two golden triangles joined at base
|
|
1652
|
+
const topY = -r;
|
|
1653
|
+
const bottomY = r * (1 / phi);
|
|
1654
|
+
const midY = r * (1 / phi - 1) * 0.3;
|
|
1655
|
+
const wingX = r * 0.6;
|
|
1656
|
+
ctx.moveTo(0, topY);
|
|
1657
|
+
ctx.lineTo(wingX, midY);
|
|
1658
|
+
ctx.lineTo(0, bottomY);
|
|
1659
|
+
ctx.lineTo(-wingX, midY);
|
|
1660
|
+
} else {
|
|
1661
|
+
// Dart: concave quadrilateral
|
|
1662
|
+
const topY = -r;
|
|
1663
|
+
const bottomY = r * 0.3;
|
|
1664
|
+
const midY = -r * 0.1;
|
|
1665
|
+
const wingX = r * 0.5;
|
|
1666
|
+
ctx.moveTo(0, topY);
|
|
1667
|
+
ctx.lineTo(wingX, midY);
|
|
1668
|
+
ctx.lineTo(0, bottomY);
|
|
1669
|
+
ctx.lineTo(-wingX, midY);
|
|
1670
|
+
}
|
|
1671
|
+
ctx.closePath();
|
|
1672
|
+
};
|
|
1673
|
+
const $a7241acce45d5513$export$1fc0aedbabd73399 = (ctx, size, config)=>{
|
|
1674
|
+
const r = size / 2;
|
|
1675
|
+
// Vertices of equilateral triangle
|
|
1676
|
+
const verts = [];
|
|
1677
|
+
for(let i = 0; i < 3; i++){
|
|
1678
|
+
const angle = i / 3 * Math.PI * 2 - Math.PI / 2;
|
|
1679
|
+
verts.push({
|
|
1680
|
+
x: Math.cos(angle) * r * 0.7,
|
|
1681
|
+
y: Math.sin(angle) * r * 0.7
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
// Side length = distance between vertices
|
|
1685
|
+
const sideLen = Math.hypot(verts[1].x - verts[0].x, verts[1].y - verts[0].y);
|
|
1686
|
+
ctx.beginPath();
|
|
1687
|
+
for(let i = 0; i < 3; i++){
|
|
1688
|
+
const from = verts[(i + 1) % 3];
|
|
1689
|
+
const to = verts[(i + 2) % 3];
|
|
1690
|
+
const center = verts[i];
|
|
1691
|
+
const startAngle = Math.atan2(from.y - center.y, from.x - center.x);
|
|
1692
|
+
const endAngle = Math.atan2(to.y - center.y, to.x - center.x);
|
|
1693
|
+
if (i === 0) ctx.moveTo(from.x, from.y);
|
|
1694
|
+
ctx.arc(center.x, center.y, sideLen, startAngle, endAngle);
|
|
1695
|
+
}
|
|
1696
|
+
ctx.closePath();
|
|
1697
|
+
};
|
|
1698
|
+
const $a7241acce45d5513$export$ef7b5e0c19a21fd1 = (ctx, size, config)=>{
|
|
1699
|
+
const rng = config?.rng ?? Math.random;
|
|
1700
|
+
const r = size / 2;
|
|
1701
|
+
const dotCount = 15 + Math.floor(rng() * 25); // 15-39 dots
|
|
1702
|
+
const clusterTightness = 0.3 + rng() * 0.5;
|
|
1703
|
+
ctx.beginPath();
|
|
1704
|
+
for(let i = 0; i < dotCount; i++){
|
|
1705
|
+
// Gaussian-ish distribution via Box-Muller approximation
|
|
1706
|
+
const u1 = Math.max(0.001, rng());
|
|
1707
|
+
const u2 = rng();
|
|
1708
|
+
const mag = Math.sqrt(-2 * Math.log(u1)) * clusterTightness;
|
|
1709
|
+
const angle = u2 * Math.PI * 2;
|
|
1710
|
+
const dx = Math.cos(angle) * mag * r;
|
|
1711
|
+
const dy = Math.sin(angle) * mag * r;
|
|
1712
|
+
const dotR = r * (0.02 + rng() * 0.04);
|
|
1713
|
+
ctx.moveTo(dx + dotR, dy);
|
|
1714
|
+
ctx.arc(dx, dy, dotR, 0, Math.PI * 2);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
const $a7241acce45d5513$export$f15df8ab60dfcc9a = (ctx, size, config)=>{
|
|
1718
|
+
const rng = config?.rng ?? Math.random;
|
|
1719
|
+
const r = size / 2;
|
|
1720
|
+
const angle1 = rng() * Math.PI;
|
|
1721
|
+
const angle2 = angle1 + Math.PI / 2 + (rng() - 0.5) * 0.3;
|
|
1722
|
+
const spacing = r * (0.08 + rng() * 0.08);
|
|
1723
|
+
const hasCross = rng() < 0.6;
|
|
1724
|
+
// Draw bounding shape (ellipse)
|
|
1725
|
+
const rx = r * (0.7 + rng() * 0.3);
|
|
1726
|
+
const ry = r * (0.5 + rng() * 0.3);
|
|
1727
|
+
// Outer boundary
|
|
1728
|
+
ctx.beginPath();
|
|
1729
|
+
ctx.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2);
|
|
1730
|
+
// Hatch lines clipped to the ellipse
|
|
1731
|
+
const cos1 = Math.cos(angle1);
|
|
1732
|
+
const sin1 = Math.sin(angle1);
|
|
1733
|
+
for(let d = -r; d <= r; d += spacing){
|
|
1734
|
+
const lx1 = d * cos1 - r * sin1;
|
|
1735
|
+
const ly1 = d * sin1 + r * cos1;
|
|
1736
|
+
const lx2 = d * cos1 + r * sin1;
|
|
1737
|
+
const ly2 = d * sin1 - r * cos1;
|
|
1738
|
+
ctx.moveTo(lx1, ly1);
|
|
1739
|
+
ctx.lineTo(lx2, ly2);
|
|
1740
|
+
}
|
|
1741
|
+
if (hasCross) {
|
|
1742
|
+
const cos2 = Math.cos(angle2);
|
|
1743
|
+
const sin2 = Math.sin(angle2);
|
|
1744
|
+
for(let d = -r; d <= r; d += spacing * 1.3){
|
|
1745
|
+
const lx1 = d * cos2 - r * sin2;
|
|
1746
|
+
const ly1 = d * sin2 + r * cos2;
|
|
1747
|
+
const lx2 = d * cos2 + r * sin2;
|
|
1748
|
+
const ly2 = d * sin2 - r * cos2;
|
|
1749
|
+
ctx.moveTo(lx1, ly1);
|
|
1750
|
+
ctx.lineTo(lx2, ly2);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1460
1754
|
const $a7241acce45d5513$export$40cfb4c637f2fbb5 = {
|
|
1461
1755
|
blob: $a7241acce45d5513$export$580f80cfb9de73bc,
|
|
1462
1756
|
ngon: $a7241acce45d5513$export$7a6094023f0902a6,
|
|
@@ -1464,7 +1758,18 @@ const $a7241acce45d5513$export$40cfb4c637f2fbb5 = {
|
|
|
1464
1758
|
superellipse: $a7241acce45d5513$export$1db9219b4f34658c,
|
|
1465
1759
|
spirograph: $a7241acce45d5513$export$b027c64d22b01985,
|
|
1466
1760
|
waveRing: $a7241acce45d5513$export$7608ccd03bfb705d,
|
|
1467
|
-
rose: $a7241acce45d5513$export$11a377e7498bb523
|
|
1761
|
+
rose: $a7241acce45d5513$export$11a377e7498bb523,
|
|
1762
|
+
shardField: $a7241acce45d5513$export$76b6526575ea179b,
|
|
1763
|
+
voronoiCell: $a7241acce45d5513$export$ed9ff98da5b05073,
|
|
1764
|
+
crescent: $a7241acce45d5513$export$e0452d9a794fe7e5,
|
|
1765
|
+
tendril: $a7241acce45d5513$export$38bfe5eb52137e01,
|
|
1766
|
+
cloudForm: $a7241acce45d5513$export$105caa8cfd63c422,
|
|
1767
|
+
inkSplat: $a7241acce45d5513$export$e181e5bd3c539569,
|
|
1768
|
+
geodesicDome: $a7241acce45d5513$export$155b4780b4c6bb7b,
|
|
1769
|
+
penroseTile: $a7241acce45d5513$export$9a7e648f11155172,
|
|
1770
|
+
reuleauxTriangle: $a7241acce45d5513$export$1fc0aedbabd73399,
|
|
1771
|
+
dotCluster: $a7241acce45d5513$export$ef7b5e0c19a21fd1,
|
|
1772
|
+
crosshatchPatch: $a7241acce45d5513$export$f15df8ab60dfcc9a
|
|
1468
1773
|
};
|
|
1469
1774
|
|
|
1470
1775
|
|
|
@@ -1499,7 +1804,13 @@ const $9beb8f41637c29fd$var$RENDER_STYLES = [
|
|
|
1499
1804
|
"dashed",
|
|
1500
1805
|
"watercolor",
|
|
1501
1806
|
"hatched",
|
|
1502
|
-
"incomplete"
|
|
1807
|
+
"incomplete",
|
|
1808
|
+
"stipple",
|
|
1809
|
+
"stencil",
|
|
1810
|
+
"noise-grain",
|
|
1811
|
+
"wood-grain",
|
|
1812
|
+
"marble-vein",
|
|
1813
|
+
"fabric-weave"
|
|
1503
1814
|
];
|
|
1504
1815
|
function $9beb8f41637c29fd$export$9fd4e64b2acd410e(rng) {
|
|
1505
1816
|
return $9beb8f41637c29fd$var$RENDER_STYLES[Math.floor(rng() * $9beb8f41637c29fd$var$RENDER_STYLES.length)];
|
|
@@ -1673,6 +1984,213 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1673
1984
|
ctx.lineDashOffset = 0;
|
|
1674
1985
|
break;
|
|
1675
1986
|
}
|
|
1987
|
+
case "stipple":
|
|
1988
|
+
{
|
|
1989
|
+
// Dot-fill texture — clip to shape, then scatter dots
|
|
1990
|
+
const savedAlphaS = ctx.globalAlpha;
|
|
1991
|
+
ctx.globalAlpha = savedAlphaS * 0.15;
|
|
1992
|
+
ctx.fill(); // ghost fill
|
|
1993
|
+
ctx.globalAlpha = savedAlphaS;
|
|
1994
|
+
ctx.save();
|
|
1995
|
+
ctx.clip();
|
|
1996
|
+
const dotSpacing = Math.max(2, size * 0.03);
|
|
1997
|
+
const extent = size * 0.55;
|
|
1998
|
+
ctx.globalAlpha = savedAlphaS * 0.7;
|
|
1999
|
+
for(let dx = -extent; dx <= extent; dx += dotSpacing)for(let dy = -extent; dy <= extent; dy += dotSpacing){
|
|
2000
|
+
// Jitter each dot position for organic feel
|
|
2001
|
+
const jx = rng ? (rng() - 0.5) * dotSpacing * 0.6 : 0;
|
|
2002
|
+
const jy = rng ? (rng() - 0.5) * dotSpacing * 0.6 : 0;
|
|
2003
|
+
const dotR = rng ? dotSpacing * (0.15 + rng() * 0.2) : dotSpacing * 0.2;
|
|
2004
|
+
ctx.beginPath();
|
|
2005
|
+
ctx.arc(dx + jx, dy + jy, dotR, 0, Math.PI * 2);
|
|
2006
|
+
ctx.fill();
|
|
2007
|
+
}
|
|
2008
|
+
ctx.restore();
|
|
2009
|
+
ctx.globalAlpha = savedAlphaS;
|
|
2010
|
+
// Outline
|
|
2011
|
+
ctx.globalAlpha *= 0.4;
|
|
2012
|
+
ctx.stroke();
|
|
2013
|
+
ctx.globalAlpha /= 0.4;
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
case "stencil":
|
|
2017
|
+
{
|
|
2018
|
+
// Negative-space cutout — fill a rectangle, then erase the shape
|
|
2019
|
+
const savedAlphaSt = ctx.globalAlpha;
|
|
2020
|
+
// Fill a bounding area with the stroke color
|
|
2021
|
+
ctx.globalAlpha = savedAlphaSt * 0.5;
|
|
2022
|
+
ctx.fillStyle = strokeColor;
|
|
2023
|
+
ctx.fillRect(-size * 0.6, -size * 0.6, size * 1.2, size * 1.2);
|
|
2024
|
+
// Cut out the shape using destination-out
|
|
2025
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2026
|
+
ctx.globalAlpha = 1;
|
|
2027
|
+
ctx.fill();
|
|
2028
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2029
|
+
ctx.globalAlpha = savedAlphaSt;
|
|
2030
|
+
// Subtle outline of the cutout
|
|
2031
|
+
ctx.globalAlpha *= 0.3;
|
|
2032
|
+
ctx.stroke();
|
|
2033
|
+
ctx.globalAlpha /= 0.3;
|
|
2034
|
+
break;
|
|
2035
|
+
}
|
|
2036
|
+
case "noise-grain":
|
|
2037
|
+
{
|
|
2038
|
+
// Procedural noise grain texture clipped to shape boundary
|
|
2039
|
+
const savedAlphaN = ctx.globalAlpha;
|
|
2040
|
+
ctx.globalAlpha = savedAlphaN * 0.25;
|
|
2041
|
+
ctx.fill(); // base tint
|
|
2042
|
+
ctx.globalAlpha = savedAlphaN;
|
|
2043
|
+
ctx.save();
|
|
2044
|
+
ctx.clip();
|
|
2045
|
+
const grainSpacing = Math.max(1.5, size * 0.015);
|
|
2046
|
+
const extentN = size * 0.55;
|
|
2047
|
+
ctx.globalAlpha = savedAlphaN * 0.6;
|
|
2048
|
+
for(let gx = -extentN; gx <= extentN; gx += grainSpacing)for(let gy = -extentN; gy <= extentN; gy += grainSpacing){
|
|
2049
|
+
if (!rng) break;
|
|
2050
|
+
const jx = (rng() - 0.5) * grainSpacing * 1.2;
|
|
2051
|
+
const jy = (rng() - 0.5) * grainSpacing * 1.2;
|
|
2052
|
+
const brightness = rng() > 0.5 ? 255 : 0;
|
|
2053
|
+
const dotAlpha = 0.15 + rng() * 0.35;
|
|
2054
|
+
ctx.globalAlpha = savedAlphaN * dotAlpha;
|
|
2055
|
+
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
2056
|
+
const dotSize = grainSpacing * (0.3 + rng() * 0.5);
|
|
2057
|
+
ctx.fillRect(gx + jx, gy + jy, dotSize, dotSize);
|
|
2058
|
+
}
|
|
2059
|
+
ctx.restore();
|
|
2060
|
+
ctx.fillStyle = fillColor;
|
|
2061
|
+
ctx.globalAlpha = savedAlphaN;
|
|
2062
|
+
ctx.globalAlpha *= 0.4;
|
|
2063
|
+
ctx.stroke();
|
|
2064
|
+
ctx.globalAlpha /= 0.4;
|
|
2065
|
+
break;
|
|
2066
|
+
}
|
|
2067
|
+
case "wood-grain":
|
|
2068
|
+
{
|
|
2069
|
+
// Parallel wavy lines simulating wood grain, clipped to shape
|
|
2070
|
+
const savedAlphaW = ctx.globalAlpha;
|
|
2071
|
+
ctx.globalAlpha = savedAlphaW * 0.2;
|
|
2072
|
+
ctx.fill(); // base tint
|
|
2073
|
+
ctx.globalAlpha = savedAlphaW;
|
|
2074
|
+
ctx.save();
|
|
2075
|
+
ctx.clip();
|
|
2076
|
+
const grainLineSpacing = Math.max(2, size * 0.035);
|
|
2077
|
+
const extentW = size * 0.55;
|
|
2078
|
+
const waveFreq = rng ? 3 + rng() * 5 : 5;
|
|
2079
|
+
const waveAmp = rng ? size * (0.01 + rng() * 0.03) : size * 0.02;
|
|
2080
|
+
const grainAngle = rng ? rng() * Math.PI : Math.PI * 0.25;
|
|
2081
|
+
ctx.lineWidth = Math.max(0.5, strokeWidth * 0.3);
|
|
2082
|
+
ctx.globalAlpha = savedAlphaW * 0.5;
|
|
2083
|
+
const cosG = Math.cos(grainAngle);
|
|
2084
|
+
const sinG = Math.sin(grainAngle);
|
|
2085
|
+
for(let d = -extentW; d <= extentW; d += grainLineSpacing){
|
|
2086
|
+
ctx.beginPath();
|
|
2087
|
+
for(let t = -extentW; t <= extentW; t += 2){
|
|
2088
|
+
const wave = Math.sin(t / extentW * waveFreq * Math.PI) * waveAmp;
|
|
2089
|
+
const px = t * cosG - (d + wave) * sinG;
|
|
2090
|
+
const py = t * sinG + (d + wave) * cosG;
|
|
2091
|
+
if (t === -extentW) ctx.moveTo(px, py);
|
|
2092
|
+
else ctx.lineTo(px, py);
|
|
2093
|
+
}
|
|
2094
|
+
ctx.stroke();
|
|
2095
|
+
}
|
|
2096
|
+
ctx.restore();
|
|
2097
|
+
ctx.globalAlpha = savedAlphaW;
|
|
2098
|
+
ctx.globalAlpha *= 0.35;
|
|
2099
|
+
ctx.stroke();
|
|
2100
|
+
ctx.globalAlpha /= 0.35;
|
|
2101
|
+
break;
|
|
2102
|
+
}
|
|
2103
|
+
case "marble-vein":
|
|
2104
|
+
{
|
|
2105
|
+
// Branching vein lines on a soft fill, clipped to shape
|
|
2106
|
+
const savedAlphaM = ctx.globalAlpha;
|
|
2107
|
+
ctx.globalAlpha = savedAlphaM * 0.35;
|
|
2108
|
+
ctx.fill(); // soft base
|
|
2109
|
+
ctx.globalAlpha = savedAlphaM;
|
|
2110
|
+
ctx.save();
|
|
2111
|
+
ctx.clip();
|
|
2112
|
+
const veinCount = rng ? 2 + Math.floor(rng() * 3) : 3;
|
|
2113
|
+
const extentM = size * 0.45;
|
|
2114
|
+
ctx.lineWidth = Math.max(0.5, strokeWidth * 0.5);
|
|
2115
|
+
ctx.globalAlpha = savedAlphaM * 0.4;
|
|
2116
|
+
for(let v = 0; v < veinCount; v++){
|
|
2117
|
+
const startX = rng ? (rng() - 0.5) * extentM * 2 : 0;
|
|
2118
|
+
const startY = rng ? -extentM + rng() * extentM * 0.5 : -extentM;
|
|
2119
|
+
let vx = startX;
|
|
2120
|
+
let vy = startY;
|
|
2121
|
+
const steps = 15 + (rng ? Math.floor(rng() * 15) : 10);
|
|
2122
|
+
const stepLen = size * 0.04;
|
|
2123
|
+
ctx.beginPath();
|
|
2124
|
+
ctx.moveTo(vx, vy);
|
|
2125
|
+
for(let s = 0; s < steps; s++){
|
|
2126
|
+
const drift = rng ? (rng() - 0.5) * stepLen * 1.5 : 0;
|
|
2127
|
+
vx += drift;
|
|
2128
|
+
vy += stepLen;
|
|
2129
|
+
ctx.lineTo(vx, vy);
|
|
2130
|
+
// Branch ~20% of the time
|
|
2131
|
+
if (rng && rng() < 0.2 && s > 2 && s < steps - 3) {
|
|
2132
|
+
const branchDir = rng() < 0.5 ? -1 : 1;
|
|
2133
|
+
let bx = vx;
|
|
2134
|
+
let by = vy;
|
|
2135
|
+
const bSteps = 3 + Math.floor(rng() * 5);
|
|
2136
|
+
ctx.moveTo(bx, by);
|
|
2137
|
+
for(let bs = 0; bs < bSteps; bs++){
|
|
2138
|
+
bx += branchDir * stepLen * (0.5 + rng() * 0.5);
|
|
2139
|
+
by += stepLen * 0.6;
|
|
2140
|
+
ctx.lineTo(bx, by);
|
|
2141
|
+
}
|
|
2142
|
+
ctx.moveTo(vx, vy); // return to main vein
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
ctx.stroke();
|
|
2146
|
+
}
|
|
2147
|
+
ctx.restore();
|
|
2148
|
+
ctx.globalAlpha = savedAlphaM;
|
|
2149
|
+
ctx.globalAlpha *= 0.3;
|
|
2150
|
+
ctx.stroke();
|
|
2151
|
+
ctx.globalAlpha /= 0.3;
|
|
2152
|
+
break;
|
|
2153
|
+
}
|
|
2154
|
+
case "fabric-weave":
|
|
2155
|
+
{
|
|
2156
|
+
// Interlocking horizontal/vertical threads clipped to shape
|
|
2157
|
+
const savedAlphaF = ctx.globalAlpha;
|
|
2158
|
+
ctx.globalAlpha = savedAlphaF * 0.15;
|
|
2159
|
+
ctx.fill(); // ghost base
|
|
2160
|
+
ctx.globalAlpha = savedAlphaF;
|
|
2161
|
+
ctx.save();
|
|
2162
|
+
ctx.clip();
|
|
2163
|
+
const threadSpacing = Math.max(2, size * 0.04);
|
|
2164
|
+
const extentF = size * 0.55;
|
|
2165
|
+
ctx.lineWidth = Math.max(0.8, threadSpacing * 0.5);
|
|
2166
|
+
ctx.globalAlpha = savedAlphaF * 0.55;
|
|
2167
|
+
// Horizontal threads
|
|
2168
|
+
for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
|
|
2169
|
+
ctx.beginPath();
|
|
2170
|
+
ctx.moveTo(-extentF, y);
|
|
2171
|
+
ctx.lineTo(extentF, y);
|
|
2172
|
+
ctx.stroke();
|
|
2173
|
+
}
|
|
2174
|
+
// Vertical threads (offset by half spacing for weave effect)
|
|
2175
|
+
ctx.globalAlpha = savedAlphaF * 0.45;
|
|
2176
|
+
ctx.strokeStyle = fillColor;
|
|
2177
|
+
for(let x = -extentF; x <= extentF; x += threadSpacing * 2){
|
|
2178
|
+
ctx.beginPath();
|
|
2179
|
+
for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
|
|
2180
|
+
// Over-under: draw segment, skip segment
|
|
2181
|
+
ctx.moveTo(x, y);
|
|
2182
|
+
ctx.lineTo(x, y + threadSpacing);
|
|
2183
|
+
}
|
|
2184
|
+
ctx.stroke();
|
|
2185
|
+
}
|
|
2186
|
+
ctx.strokeStyle = strokeColor;
|
|
2187
|
+
ctx.restore();
|
|
2188
|
+
ctx.globalAlpha = savedAlphaF;
|
|
2189
|
+
ctx.globalAlpha *= 0.3;
|
|
2190
|
+
ctx.stroke();
|
|
2191
|
+
ctx.globalAlpha /= 0.3;
|
|
2192
|
+
break;
|
|
2193
|
+
}
|
|
1676
2194
|
case "fill-and-stroke":
|
|
1677
2195
|
default:
|
|
1678
2196
|
ctx.fill();
|
|
@@ -1719,6 +2237,64 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1719
2237
|
});
|
|
1720
2238
|
ctx.restore();
|
|
1721
2239
|
}
|
|
2240
|
+
function $9beb8f41637c29fd$export$8bd8bbd1a8e53689(ctx, shape, x, y, config) {
|
|
2241
|
+
const { mirrorAxis: mirrorAxis = "horizontal", mirrorGap: mirrorGap = 0 } = config;
|
|
2242
|
+
// Draw the primary shape
|
|
2243
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config);
|
|
2244
|
+
// Draw the mirrored copy
|
|
2245
|
+
ctx.save();
|
|
2246
|
+
const savedAlpha = ctx.globalAlpha;
|
|
2247
|
+
ctx.globalAlpha = savedAlpha * 0.7; // mirror is slightly softer
|
|
2248
|
+
switch(mirrorAxis){
|
|
2249
|
+
case "horizontal":
|
|
2250
|
+
// Reflect across vertical axis at shape position
|
|
2251
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y + mirrorGap, {
|
|
2252
|
+
...config,
|
|
2253
|
+
rotation: -(config.rotation || 0),
|
|
2254
|
+
size: config.size * 0.95
|
|
2255
|
+
});
|
|
2256
|
+
break;
|
|
2257
|
+
case "vertical":
|
|
2258
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x + mirrorGap, y, {
|
|
2259
|
+
...config,
|
|
2260
|
+
rotation: 180 - (config.rotation || 0),
|
|
2261
|
+
size: config.size * 0.95
|
|
2262
|
+
});
|
|
2263
|
+
break;
|
|
2264
|
+
case "diagonal":
|
|
2265
|
+
// Reflect across 45° axis
|
|
2266
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x + mirrorGap * 0.7, y + mirrorGap * 0.7, {
|
|
2267
|
+
...config,
|
|
2268
|
+
rotation: 90 - (config.rotation || 0),
|
|
2269
|
+
size: config.size * 0.9
|
|
2270
|
+
});
|
|
2271
|
+
break;
|
|
2272
|
+
case "radial-4":
|
|
2273
|
+
// Four-way radial mirror
|
|
2274
|
+
for(let i = 1; i < 4; i++){
|
|
2275
|
+
const angle = i / 4 * Math.PI * 2;
|
|
2276
|
+
const mx = x + Math.cos(angle) * mirrorGap;
|
|
2277
|
+
const my = y + Math.sin(angle) * mirrorGap;
|
|
2278
|
+
ctx.globalAlpha = savedAlpha * (0.7 - i * 0.1);
|
|
2279
|
+
$9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, mx, my, {
|
|
2280
|
+
...config,
|
|
2281
|
+
rotation: (config.rotation || 0) + i * 90,
|
|
2282
|
+
size: config.size * (0.95 - i * 0.05)
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
break;
|
|
2286
|
+
}
|
|
2287
|
+
ctx.globalAlpha = savedAlpha;
|
|
2288
|
+
ctx.restore();
|
|
2289
|
+
}
|
|
2290
|
+
function $9beb8f41637c29fd$export$879206e23912d1a9(rng) {
|
|
2291
|
+
const roll = rng();
|
|
2292
|
+
if (roll < 0.60) return null;
|
|
2293
|
+
if (roll < 0.75) return "horizontal";
|
|
2294
|
+
if (roll < 0.87) return "vertical";
|
|
2295
|
+
if (roll < 0.95) return "diagonal";
|
|
2296
|
+
return "radial-4";
|
|
2297
|
+
}
|
|
1722
2298
|
|
|
1723
2299
|
|
|
1724
2300
|
|
|
@@ -2221,7 +2797,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
|
2221
2797
|
bestStyles: [
|
|
2222
2798
|
"fill-only",
|
|
2223
2799
|
"watercolor",
|
|
2224
|
-
"fill-and-stroke"
|
|
2800
|
+
"fill-and-stroke",
|
|
2801
|
+
"wood-grain"
|
|
2225
2802
|
]
|
|
2226
2803
|
},
|
|
2227
2804
|
spirograph: {
|
|
@@ -2277,6 +2854,202 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
|
2277
2854
|
"fill-only",
|
|
2278
2855
|
"watercolor"
|
|
2279
2856
|
]
|
|
2857
|
+
},
|
|
2858
|
+
// ── New procedural shapes ─────────────────────────────────────
|
|
2859
|
+
shardField: {
|
|
2860
|
+
tier: 2,
|
|
2861
|
+
minSizeFraction: 0.1,
|
|
2862
|
+
maxSizeFraction: 0.7,
|
|
2863
|
+
affinities: [
|
|
2864
|
+
"voronoiCell",
|
|
2865
|
+
"diamond",
|
|
2866
|
+
"triangle",
|
|
2867
|
+
"penroseTile"
|
|
2868
|
+
],
|
|
2869
|
+
category: "procedural",
|
|
2870
|
+
heroCandidate: false,
|
|
2871
|
+
bestStyles: [
|
|
2872
|
+
"fill-and-stroke",
|
|
2873
|
+
"stroke-only",
|
|
2874
|
+
"fill-only"
|
|
2875
|
+
]
|
|
2876
|
+
},
|
|
2877
|
+
voronoiCell: {
|
|
2878
|
+
tier: 1,
|
|
2879
|
+
minSizeFraction: 0.08,
|
|
2880
|
+
maxSizeFraction: 0.9,
|
|
2881
|
+
affinities: [
|
|
2882
|
+
"shardField",
|
|
2883
|
+
"ngon",
|
|
2884
|
+
"superellipse",
|
|
2885
|
+
"blob"
|
|
2886
|
+
],
|
|
2887
|
+
category: "procedural",
|
|
2888
|
+
heroCandidate: false,
|
|
2889
|
+
bestStyles: [
|
|
2890
|
+
"fill-and-stroke",
|
|
2891
|
+
"fill-only",
|
|
2892
|
+
"watercolor",
|
|
2893
|
+
"marble-vein"
|
|
2894
|
+
]
|
|
2895
|
+
},
|
|
2896
|
+
crescent: {
|
|
2897
|
+
tier: 1,
|
|
2898
|
+
minSizeFraction: 0.1,
|
|
2899
|
+
maxSizeFraction: 1.0,
|
|
2900
|
+
affinities: [
|
|
2901
|
+
"circle",
|
|
2902
|
+
"blob",
|
|
2903
|
+
"cloudForm",
|
|
2904
|
+
"vesicaPiscis"
|
|
2905
|
+
],
|
|
2906
|
+
category: "procedural",
|
|
2907
|
+
heroCandidate: true,
|
|
2908
|
+
bestStyles: [
|
|
2909
|
+
"fill-only",
|
|
2910
|
+
"watercolor",
|
|
2911
|
+
"fill-and-stroke"
|
|
2912
|
+
]
|
|
2913
|
+
},
|
|
2914
|
+
tendril: {
|
|
2915
|
+
tier: 2,
|
|
2916
|
+
minSizeFraction: 0.1,
|
|
2917
|
+
maxSizeFraction: 0.8,
|
|
2918
|
+
affinities: [
|
|
2919
|
+
"blob",
|
|
2920
|
+
"inkSplat",
|
|
2921
|
+
"lissajous",
|
|
2922
|
+
"fibonacciSpiral"
|
|
2923
|
+
],
|
|
2924
|
+
category: "procedural",
|
|
2925
|
+
heroCandidate: false,
|
|
2926
|
+
bestStyles: [
|
|
2927
|
+
"fill-only",
|
|
2928
|
+
"watercolor",
|
|
2929
|
+
"fill-and-stroke"
|
|
2930
|
+
]
|
|
2931
|
+
},
|
|
2932
|
+
cloudForm: {
|
|
2933
|
+
tier: 1,
|
|
2934
|
+
minSizeFraction: 0.15,
|
|
2935
|
+
maxSizeFraction: 1.0,
|
|
2936
|
+
affinities: [
|
|
2937
|
+
"blob",
|
|
2938
|
+
"circle",
|
|
2939
|
+
"crescent",
|
|
2940
|
+
"superellipse"
|
|
2941
|
+
],
|
|
2942
|
+
category: "procedural",
|
|
2943
|
+
heroCandidate: false,
|
|
2944
|
+
bestStyles: [
|
|
2945
|
+
"fill-only",
|
|
2946
|
+
"watercolor"
|
|
2947
|
+
]
|
|
2948
|
+
},
|
|
2949
|
+
inkSplat: {
|
|
2950
|
+
tier: 2,
|
|
2951
|
+
minSizeFraction: 0.1,
|
|
2952
|
+
maxSizeFraction: 0.8,
|
|
2953
|
+
affinities: [
|
|
2954
|
+
"blob",
|
|
2955
|
+
"tendril",
|
|
2956
|
+
"shardField",
|
|
2957
|
+
"star"
|
|
2958
|
+
],
|
|
2959
|
+
category: "procedural",
|
|
2960
|
+
heroCandidate: false,
|
|
2961
|
+
bestStyles: [
|
|
2962
|
+
"fill-only",
|
|
2963
|
+
"watercolor",
|
|
2964
|
+
"fill-and-stroke"
|
|
2965
|
+
]
|
|
2966
|
+
},
|
|
2967
|
+
geodesicDome: {
|
|
2968
|
+
tier: 2,
|
|
2969
|
+
minSizeFraction: 0.2,
|
|
2970
|
+
maxSizeFraction: 0.9,
|
|
2971
|
+
affinities: [
|
|
2972
|
+
"metatronsCube",
|
|
2973
|
+
"platonicSolid",
|
|
2974
|
+
"hexagon",
|
|
2975
|
+
"triangle"
|
|
2976
|
+
],
|
|
2977
|
+
category: "procedural",
|
|
2978
|
+
heroCandidate: true,
|
|
2979
|
+
bestStyles: [
|
|
2980
|
+
"stroke-only",
|
|
2981
|
+
"dashed",
|
|
2982
|
+
"double-stroke"
|
|
2983
|
+
]
|
|
2984
|
+
},
|
|
2985
|
+
penroseTile: {
|
|
2986
|
+
tier: 2,
|
|
2987
|
+
minSizeFraction: 0.06,
|
|
2988
|
+
maxSizeFraction: 0.6,
|
|
2989
|
+
affinities: [
|
|
2990
|
+
"diamond",
|
|
2991
|
+
"triangle",
|
|
2992
|
+
"shardField",
|
|
2993
|
+
"voronoiCell"
|
|
2994
|
+
],
|
|
2995
|
+
category: "procedural",
|
|
2996
|
+
heroCandidate: false,
|
|
2997
|
+
bestStyles: [
|
|
2998
|
+
"fill-and-stroke",
|
|
2999
|
+
"fill-only",
|
|
3000
|
+
"double-stroke"
|
|
3001
|
+
]
|
|
3002
|
+
},
|
|
3003
|
+
reuleauxTriangle: {
|
|
3004
|
+
tier: 1,
|
|
3005
|
+
minSizeFraction: 0.08,
|
|
3006
|
+
maxSizeFraction: 1.0,
|
|
3007
|
+
affinities: [
|
|
3008
|
+
"triangle",
|
|
3009
|
+
"circle",
|
|
3010
|
+
"superellipse",
|
|
3011
|
+
"vesicaPiscis"
|
|
3012
|
+
],
|
|
3013
|
+
category: "procedural",
|
|
3014
|
+
heroCandidate: true,
|
|
3015
|
+
bestStyles: [
|
|
3016
|
+
"fill-and-stroke",
|
|
3017
|
+
"fill-only",
|
|
3018
|
+
"watercolor"
|
|
3019
|
+
]
|
|
3020
|
+
},
|
|
3021
|
+
dotCluster: {
|
|
3022
|
+
tier: 3,
|
|
3023
|
+
minSizeFraction: 0.05,
|
|
3024
|
+
maxSizeFraction: 0.5,
|
|
3025
|
+
affinities: [
|
|
3026
|
+
"cloudForm",
|
|
3027
|
+
"inkSplat",
|
|
3028
|
+
"blob"
|
|
3029
|
+
],
|
|
3030
|
+
category: "procedural",
|
|
3031
|
+
heroCandidate: false,
|
|
3032
|
+
bestStyles: [
|
|
3033
|
+
"fill-only",
|
|
3034
|
+
"stipple"
|
|
3035
|
+
]
|
|
3036
|
+
},
|
|
3037
|
+
crosshatchPatch: {
|
|
3038
|
+
tier: 3,
|
|
3039
|
+
minSizeFraction: 0.1,
|
|
3040
|
+
maxSizeFraction: 0.6,
|
|
3041
|
+
affinities: [
|
|
3042
|
+
"voronoiCell",
|
|
3043
|
+
"ngon",
|
|
3044
|
+
"superellipse"
|
|
3045
|
+
],
|
|
3046
|
+
category: "procedural",
|
|
3047
|
+
heroCandidate: false,
|
|
3048
|
+
bestStyles: [
|
|
3049
|
+
"stroke-only",
|
|
3050
|
+
"hatched",
|
|
3051
|
+
"fabric-weave"
|
|
3052
|
+
]
|
|
2280
3053
|
}
|
|
2281
3054
|
};
|
|
2282
3055
|
function $24064302523652b1$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
|
|
@@ -2351,6 +3124,81 @@ function $24064302523652b1$export$4a95df8944b5033b(rng, shapeNames, archetypeNam
|
|
|
2351
3124
|
accents: accents
|
|
2352
3125
|
};
|
|
2353
3126
|
}
|
|
3127
|
+
if (archetypeName === "shattered-glass") {
|
|
3128
|
+
// Favor angular, fragmented shapes
|
|
3129
|
+
const shardBoost = available.filter((s)=>[
|
|
3130
|
+
"shardField",
|
|
3131
|
+
"voronoiCell",
|
|
3132
|
+
"penroseTile",
|
|
3133
|
+
"diamond",
|
|
3134
|
+
"triangle",
|
|
3135
|
+
"ngon"
|
|
3136
|
+
].includes(s) && !primary.includes(s));
|
|
3137
|
+
return {
|
|
3138
|
+
primary: [
|
|
3139
|
+
...primary.filter((s)=>s !== "blob" && s !== "cloudForm"),
|
|
3140
|
+
...shardBoost.slice(0, 3)
|
|
3141
|
+
],
|
|
3142
|
+
supporting: supporting.filter((s)=>s !== "blob" && s !== "cloudForm"),
|
|
3143
|
+
accents: accents
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
if (archetypeName === "botanical") {
|
|
3147
|
+
// Favor organic, flowing shapes
|
|
3148
|
+
const botanicalBoost = available.filter((s)=>[
|
|
3149
|
+
"tendril",
|
|
3150
|
+
"cloudForm",
|
|
3151
|
+
"blob",
|
|
3152
|
+
"crescent",
|
|
3153
|
+
"rose",
|
|
3154
|
+
"inkSplat"
|
|
3155
|
+
].includes(s) && !primary.includes(s));
|
|
3156
|
+
return {
|
|
3157
|
+
primary: [
|
|
3158
|
+
...primary,
|
|
3159
|
+
...botanicalBoost.slice(0, 3)
|
|
3160
|
+
],
|
|
3161
|
+
supporting: supporting,
|
|
3162
|
+
accents: accents
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
if (archetypeName === "stipple-portrait") {
|
|
3166
|
+
// Favor small, dot-friendly shapes
|
|
3167
|
+
const stippleBoost = available.filter((s)=>[
|
|
3168
|
+
"dotCluster",
|
|
3169
|
+
"circle",
|
|
3170
|
+
"crosshatchPatch",
|
|
3171
|
+
"voronoiCell",
|
|
3172
|
+
"blob"
|
|
3173
|
+
].includes(s) && !primary.includes(s));
|
|
3174
|
+
return {
|
|
3175
|
+
primary: [
|
|
3176
|
+
...primary,
|
|
3177
|
+
...stippleBoost.slice(0, 3)
|
|
3178
|
+
],
|
|
3179
|
+
supporting: supporting,
|
|
3180
|
+
accents: accents
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
if (archetypeName === "celestial") {
|
|
3184
|
+
// Favor sacred geometry and cosmic shapes
|
|
3185
|
+
const celestialBoost = available.filter((s)=>[
|
|
3186
|
+
"crescent",
|
|
3187
|
+
"geodesicDome",
|
|
3188
|
+
"mandala",
|
|
3189
|
+
"flowerOfLife",
|
|
3190
|
+
"spirograph",
|
|
3191
|
+
"fibonacciSpiral"
|
|
3192
|
+
].includes(s) && !primary.includes(s));
|
|
3193
|
+
return {
|
|
3194
|
+
primary: [
|
|
3195
|
+
...primary,
|
|
3196
|
+
...celestialBoost.slice(0, 3)
|
|
3197
|
+
],
|
|
3198
|
+
supporting: supporting,
|
|
3199
|
+
accents: accents
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
2354
3202
|
return {
|
|
2355
3203
|
primary: primary,
|
|
2356
3204
|
supporting: supporting,
|
|
@@ -2688,6 +3536,91 @@ const $3faa2521b78398cf$var$ARCHETYPES = [
|
|
|
2688
3536
|
glowMultiplier: 1,
|
|
2689
3537
|
sizePower: 1.8,
|
|
2690
3538
|
invertForeground: false
|
|
3539
|
+
},
|
|
3540
|
+
{
|
|
3541
|
+
name: "shattered-glass",
|
|
3542
|
+
gridSize: 8,
|
|
3543
|
+
layers: 3,
|
|
3544
|
+
baseOpacity: 0.85,
|
|
3545
|
+
opacityReduction: 0.1,
|
|
3546
|
+
minShapeSize: 15,
|
|
3547
|
+
maxShapeSize: 250,
|
|
3548
|
+
backgroundStyle: "solid-dark",
|
|
3549
|
+
paletteMode: "high-contrast",
|
|
3550
|
+
preferredStyles: [
|
|
3551
|
+
"fill-and-stroke",
|
|
3552
|
+
"stroke-only",
|
|
3553
|
+
"fill-only"
|
|
3554
|
+
],
|
|
3555
|
+
flowLineMultiplier: 0,
|
|
3556
|
+
heroShape: false,
|
|
3557
|
+
glowMultiplier: 0.3,
|
|
3558
|
+
sizePower: 1.0,
|
|
3559
|
+
invertForeground: false
|
|
3560
|
+
},
|
|
3561
|
+
{
|
|
3562
|
+
name: "botanical",
|
|
3563
|
+
gridSize: 4,
|
|
3564
|
+
layers: 4,
|
|
3565
|
+
baseOpacity: 0.5,
|
|
3566
|
+
opacityReduction: 0.06,
|
|
3567
|
+
minShapeSize: 30,
|
|
3568
|
+
maxShapeSize: 400,
|
|
3569
|
+
backgroundStyle: "radial-light",
|
|
3570
|
+
paletteMode: "earth",
|
|
3571
|
+
preferredStyles: [
|
|
3572
|
+
"watercolor",
|
|
3573
|
+
"fill-only",
|
|
3574
|
+
"incomplete"
|
|
3575
|
+
],
|
|
3576
|
+
flowLineMultiplier: 3,
|
|
3577
|
+
heroShape: true,
|
|
3578
|
+
glowMultiplier: 0.2,
|
|
3579
|
+
sizePower: 1.6,
|
|
3580
|
+
invertForeground: false
|
|
3581
|
+
},
|
|
3582
|
+
{
|
|
3583
|
+
name: "stipple-portrait",
|
|
3584
|
+
gridSize: 9,
|
|
3585
|
+
layers: 2,
|
|
3586
|
+
baseOpacity: 0.8,
|
|
3587
|
+
opacityReduction: 0.05,
|
|
3588
|
+
minShapeSize: 5,
|
|
3589
|
+
maxShapeSize: 120,
|
|
3590
|
+
backgroundStyle: "solid-light",
|
|
3591
|
+
paletteMode: "monochrome",
|
|
3592
|
+
preferredStyles: [
|
|
3593
|
+
"stipple",
|
|
3594
|
+
"fill-only",
|
|
3595
|
+
"hatched"
|
|
3596
|
+
],
|
|
3597
|
+
flowLineMultiplier: 0,
|
|
3598
|
+
heroShape: false,
|
|
3599
|
+
glowMultiplier: 0,
|
|
3600
|
+
sizePower: 2.8,
|
|
3601
|
+
invertForeground: false
|
|
3602
|
+
},
|
|
3603
|
+
{
|
|
3604
|
+
name: "celestial",
|
|
3605
|
+
gridSize: 7,
|
|
3606
|
+
layers: 5,
|
|
3607
|
+
baseOpacity: 0.45,
|
|
3608
|
+
opacityReduction: 0.04,
|
|
3609
|
+
minShapeSize: 8,
|
|
3610
|
+
maxShapeSize: 450,
|
|
3611
|
+
backgroundStyle: "radial-dark",
|
|
3612
|
+
paletteMode: "neon",
|
|
3613
|
+
preferredStyles: [
|
|
3614
|
+
"fill-only",
|
|
3615
|
+
"watercolor",
|
|
3616
|
+
"stroke-only",
|
|
3617
|
+
"incomplete"
|
|
3618
|
+
],
|
|
3619
|
+
flowLineMultiplier: 2,
|
|
3620
|
+
heroShape: true,
|
|
3621
|
+
glowMultiplier: 2.5,
|
|
3622
|
+
sizePower: 2.2,
|
|
3623
|
+
invertForeground: false
|
|
2691
3624
|
}
|
|
2692
3625
|
];
|
|
2693
3626
|
function $3faa2521b78398cf$export$f1142fd7da4d6590(rng) {
|
|
@@ -2867,6 +3800,135 @@ function $b623126c6e9cbb71$var$drawBackground(ctx, style, bgStart, bgEnd, width,
|
|
|
2867
3800
|
}
|
|
2868
3801
|
}
|
|
2869
3802
|
}
|
|
3803
|
+
const $b623126c6e9cbb71$var$CONSTELLATIONS = [
|
|
3804
|
+
{
|
|
3805
|
+
name: "flanked-triangle",
|
|
3806
|
+
build: (rng, baseSize)=>{
|
|
3807
|
+
const gap = baseSize * (0.6 + rng() * 0.3);
|
|
3808
|
+
return [
|
|
3809
|
+
{
|
|
3810
|
+
dx: 0,
|
|
3811
|
+
dy: 0,
|
|
3812
|
+
shape: "triangle",
|
|
3813
|
+
size: baseSize,
|
|
3814
|
+
rotation: rng() * 360
|
|
3815
|
+
},
|
|
3816
|
+
{
|
|
3817
|
+
dx: -gap,
|
|
3818
|
+
dy: gap * 0.3,
|
|
3819
|
+
shape: "circle",
|
|
3820
|
+
size: baseSize * 0.35,
|
|
3821
|
+
rotation: 0
|
|
3822
|
+
},
|
|
3823
|
+
{
|
|
3824
|
+
dx: gap,
|
|
3825
|
+
dy: gap * 0.3,
|
|
3826
|
+
shape: "circle",
|
|
3827
|
+
size: baseSize * 0.35,
|
|
3828
|
+
rotation: 0
|
|
3829
|
+
}
|
|
3830
|
+
];
|
|
3831
|
+
}
|
|
3832
|
+
},
|
|
3833
|
+
{
|
|
3834
|
+
name: "hexagon-ring",
|
|
3835
|
+
build: (rng, baseSize)=>{
|
|
3836
|
+
const members = [];
|
|
3837
|
+
const count = 5 + Math.floor(rng() * 2);
|
|
3838
|
+
const ringR = baseSize * 0.6;
|
|
3839
|
+
for(let i = 0; i < count; i++){
|
|
3840
|
+
const angle = i / count * Math.PI * 2;
|
|
3841
|
+
members.push({
|
|
3842
|
+
dx: Math.cos(angle) * ringR,
|
|
3843
|
+
dy: Math.sin(angle) * ringR,
|
|
3844
|
+
shape: "hexagon",
|
|
3845
|
+
size: baseSize * (0.25 + rng() * 0.1),
|
|
3846
|
+
rotation: angle * 180 / Math.PI
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
return members;
|
|
3850
|
+
}
|
|
3851
|
+
},
|
|
3852
|
+
{
|
|
3853
|
+
name: "spiral-dots",
|
|
3854
|
+
build: (rng, baseSize)=>{
|
|
3855
|
+
const members = [];
|
|
3856
|
+
const count = 7 + Math.floor(rng() * 5);
|
|
3857
|
+
const turns = 1.5 + rng();
|
|
3858
|
+
for(let i = 0; i < count; i++){
|
|
3859
|
+
const t = i / count;
|
|
3860
|
+
const angle = t * Math.PI * 2 * turns;
|
|
3861
|
+
const r = t * baseSize * 0.7;
|
|
3862
|
+
members.push({
|
|
3863
|
+
dx: Math.cos(angle) * r,
|
|
3864
|
+
dy: Math.sin(angle) * r,
|
|
3865
|
+
shape: "circle",
|
|
3866
|
+
size: baseSize * (0.08 + (1 - t) * 0.12),
|
|
3867
|
+
rotation: 0
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
return members;
|
|
3871
|
+
}
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
name: "diamond-cluster",
|
|
3875
|
+
build: (rng, baseSize)=>{
|
|
3876
|
+
const gap = baseSize * 0.45;
|
|
3877
|
+
return [
|
|
3878
|
+
{
|
|
3879
|
+
dx: 0,
|
|
3880
|
+
dy: -gap,
|
|
3881
|
+
shape: "diamond",
|
|
3882
|
+
size: baseSize * 0.4,
|
|
3883
|
+
rotation: 0
|
|
3884
|
+
},
|
|
3885
|
+
{
|
|
3886
|
+
dx: gap,
|
|
3887
|
+
dy: 0,
|
|
3888
|
+
shape: "diamond",
|
|
3889
|
+
size: baseSize * 0.35,
|
|
3890
|
+
rotation: 15
|
|
3891
|
+
},
|
|
3892
|
+
{
|
|
3893
|
+
dx: 0,
|
|
3894
|
+
dy: gap,
|
|
3895
|
+
shape: "diamond",
|
|
3896
|
+
size: baseSize * 0.3,
|
|
3897
|
+
rotation: 30
|
|
3898
|
+
},
|
|
3899
|
+
{
|
|
3900
|
+
dx: -gap,
|
|
3901
|
+
dy: 0,
|
|
3902
|
+
shape: "diamond",
|
|
3903
|
+
size: baseSize * 0.35,
|
|
3904
|
+
rotation: -15
|
|
3905
|
+
}
|
|
3906
|
+
];
|
|
3907
|
+
}
|
|
3908
|
+
},
|
|
3909
|
+
{
|
|
3910
|
+
name: "crescent-pair",
|
|
3911
|
+
build: (rng, baseSize)=>{
|
|
3912
|
+
const gap = baseSize * 0.5;
|
|
3913
|
+
return [
|
|
3914
|
+
{
|
|
3915
|
+
dx: -gap * 0.4,
|
|
3916
|
+
dy: 0,
|
|
3917
|
+
shape: "crescent",
|
|
3918
|
+
size: baseSize * 0.5,
|
|
3919
|
+
rotation: rng() * 30
|
|
3920
|
+
},
|
|
3921
|
+
{
|
|
3922
|
+
dx: gap * 0.4,
|
|
3923
|
+
dy: 0,
|
|
3924
|
+
shape: "crescent",
|
|
3925
|
+
size: baseSize * 0.45,
|
|
3926
|
+
rotation: 180 + rng() * 30
|
|
3927
|
+
}
|
|
3928
|
+
];
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
];
|
|
2870
3932
|
function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
2871
3933
|
const finalConfig = {
|
|
2872
3934
|
...(0, $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f),
|
|
@@ -2953,6 +4015,65 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
2953
4015
|
ctx.stroke();
|
|
2954
4016
|
}
|
|
2955
4017
|
ctx.globalCompositeOperation = "source-over";
|
|
4018
|
+
// ── 1c. Background pattern layer — subtle textured paper ───────
|
|
4019
|
+
const bgPatternRoll = rng();
|
|
4020
|
+
if (bgPatternRoll < 0.6) {
|
|
4021
|
+
ctx.save();
|
|
4022
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
4023
|
+
const patternOpacity = 0.02 + rng() * 0.04;
|
|
4024
|
+
const patternColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
|
|
4025
|
+
if (bgPatternRoll < 0.2) {
|
|
4026
|
+
// Dot grid
|
|
4027
|
+
const dotSpacing = Math.max(8, Math.min(width, height) * (0.015 + rng() * 0.015));
|
|
4028
|
+
const dotR = dotSpacing * 0.08;
|
|
4029
|
+
ctx.globalAlpha = patternOpacity;
|
|
4030
|
+
ctx.fillStyle = patternColor;
|
|
4031
|
+
for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
|
|
4032
|
+
ctx.beginPath();
|
|
4033
|
+
ctx.arc(px, py, dotR, 0, Math.PI * 2);
|
|
4034
|
+
ctx.fill();
|
|
4035
|
+
}
|
|
4036
|
+
} else if (bgPatternRoll < 0.4) {
|
|
4037
|
+
// Diagonal lines
|
|
4038
|
+
const lineSpacing = Math.max(6, Math.min(width, height) * (0.02 + rng() * 0.02));
|
|
4039
|
+
ctx.globalAlpha = patternOpacity;
|
|
4040
|
+
ctx.strokeStyle = patternColor;
|
|
4041
|
+
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4042
|
+
const diag = Math.hypot(width, height);
|
|
4043
|
+
for(let d = -diag; d < diag; d += lineSpacing){
|
|
4044
|
+
ctx.beginPath();
|
|
4045
|
+
ctx.moveTo(d, 0);
|
|
4046
|
+
ctx.lineTo(d + height, height);
|
|
4047
|
+
ctx.stroke();
|
|
4048
|
+
}
|
|
4049
|
+
} else {
|
|
4050
|
+
// Tessellation — hexagonal grid of tiny shapes
|
|
4051
|
+
const tessSize = Math.max(10, Math.min(width, height) * (0.025 + rng() * 0.02));
|
|
4052
|
+
const tessH = tessSize * Math.sqrt(3);
|
|
4053
|
+
ctx.globalAlpha = patternOpacity * 0.7;
|
|
4054
|
+
ctx.strokeStyle = patternColor;
|
|
4055
|
+
ctx.lineWidth = 0.4 * scaleFactor;
|
|
4056
|
+
for(let row = 0; row * tessH < height + tessH; row++){
|
|
4057
|
+
const offsetX = row % 2 * tessSize * 0.75;
|
|
4058
|
+
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
|
|
4059
|
+
const hx = col * tessSize * 1.5 + offsetX;
|
|
4060
|
+
const hy = row * tessH;
|
|
4061
|
+
ctx.beginPath();
|
|
4062
|
+
for(let s = 0; s < 6; s++){
|
|
4063
|
+
const angle = Math.PI / 3 * s - Math.PI / 6;
|
|
4064
|
+
const vx = hx + Math.cos(angle) * tessSize * 0.5;
|
|
4065
|
+
const vy = hy + Math.sin(angle) * tessSize * 0.5;
|
|
4066
|
+
if (s === 0) ctx.moveTo(vx, vy);
|
|
4067
|
+
else ctx.lineTo(vx, vy);
|
|
4068
|
+
}
|
|
4069
|
+
ctx.closePath();
|
|
4070
|
+
ctx.stroke();
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
ctx.restore();
|
|
4075
|
+
}
|
|
4076
|
+
ctx.globalCompositeOperation = "source-over";
|
|
2956
4077
|
// ── 2. Composition mode ────────────────────────────────────────
|
|
2957
4078
|
const compositionMode = $b623126c6e9cbb71$var$COMPOSITION_MODES[Math.floor(rng() * $b623126c6e9cbb71$var$COMPOSITION_MODES.length)];
|
|
2958
4079
|
const symRoll = rng();
|
|
@@ -3080,6 +4201,11 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3080
4201
|
const layerRenderStyle = rng() < 0.6 ? archetype.preferredStyles[Math.floor(rng() * archetype.preferredStyles.length)] : (0, $9beb8f41637c29fd$export$9fd4e64b2acd410e)(rng);
|
|
3081
4202
|
// Atmospheric desaturation for later layers
|
|
3082
4203
|
const atmosphericDesat = layerRatio * 0.3;
|
|
4204
|
+
// Depth-of-field simulation — later layers are "further away"
|
|
4205
|
+
// Reduce stroke widths and shift colors toward the background
|
|
4206
|
+
const dofFactor = 1 - layerRatio * 0.5; // 1.0 for front layer, 0.5 for back
|
|
4207
|
+
const dofStrokeScale = 0.4 + dofFactor * 0.6; // strokes thin out with depth
|
|
4208
|
+
const dofContrastReduction = layerRatio * 0.2; // colors fade toward bg
|
|
3083
4209
|
for(let i = 0; i < numShapes; i++){
|
|
3084
4210
|
// Position from composition mode, then focal bias
|
|
3085
4211
|
const rawPos = $b623126c6e9cbb71$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
@@ -3122,8 +4248,10 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3122
4248
|
// Semi-transparent fill
|
|
3123
4249
|
const fillAlpha = 0.2 + rng() * 0.5;
|
|
3124
4250
|
const transparentFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, fillAlpha);
|
|
3125
|
-
const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor;
|
|
3126
|
-
|
|
4251
|
+
const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor * dofStrokeScale;
|
|
4252
|
+
// Depth-of-field: reduce opacity slightly for distant layers
|
|
4253
|
+
const dofOpacityScale = 1 - dofContrastReduction;
|
|
4254
|
+
ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5) * dofOpacityScale;
|
|
3127
4255
|
// Glow on sacred shapes more often — scaled by archetype
|
|
3128
4256
|
const isSacred = $b623126c6e9cbb71$var$SACRED_SHAPES.includes(shape);
|
|
3129
4257
|
const baseGlowChance = isSacred ? 0.45 : 0.2;
|
|
@@ -3142,7 +4270,48 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3142
4270
|
const shadowDist = hasGlow ? 0 : size * 0.02;
|
|
3143
4271
|
const shadowOffX = shadowDist * Math.cos(lightAngle);
|
|
3144
4272
|
const shadowOffY = shadowDist * Math.sin(lightAngle);
|
|
3145
|
-
|
|
4273
|
+
// ── 5a. Tangent placement — nudge toward nearest shape edge ──
|
|
4274
|
+
let finalX = x;
|
|
4275
|
+
let finalY = y;
|
|
4276
|
+
if (shapePositions.length > 0 && rng() < 0.25) {
|
|
4277
|
+
// Find nearest placed shape
|
|
4278
|
+
let nearestDist = Infinity;
|
|
4279
|
+
let nearestPos = null;
|
|
4280
|
+
for (const sp of shapePositions){
|
|
4281
|
+
const d = Math.hypot(x - sp.x, y - sp.y);
|
|
4282
|
+
if (d < nearestDist && d > 0) {
|
|
4283
|
+
nearestDist = d;
|
|
4284
|
+
nearestPos = sp;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (nearestPos) {
|
|
4288
|
+
// Target distance: edges kissing (sum of half-sizes)
|
|
4289
|
+
const targetDist = (size + nearestPos.size) * 0.5;
|
|
4290
|
+
if (nearestDist > targetDist * 0.5 && nearestDist < targetDist * 3) {
|
|
4291
|
+
const angle = Math.atan2(y - nearestPos.y, x - nearestPos.x);
|
|
4292
|
+
finalX = nearestPos.x + Math.cos(angle) * targetDist;
|
|
4293
|
+
finalY = nearestPos.y + Math.sin(angle) * targetDist;
|
|
4294
|
+
// Keep in bounds
|
|
4295
|
+
finalX = Math.max(0, Math.min(width, finalX));
|
|
4296
|
+
finalY = Math.max(0, Math.min(height, finalY));
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
// ── 5b. Shape mirroring — basic shapes get reflected copies ──
|
|
4301
|
+
const mirrorAxis = (0, $9beb8f41637c29fd$export$879206e23912d1a9)(rng);
|
|
4302
|
+
const isBasicShape = [
|
|
4303
|
+
"circle",
|
|
4304
|
+
"triangle",
|
|
4305
|
+
"square",
|
|
4306
|
+
"hexagon",
|
|
4307
|
+
"star",
|
|
4308
|
+
"diamond",
|
|
4309
|
+
"crescent",
|
|
4310
|
+
"penroseTile",
|
|
4311
|
+
"reuleauxTriangle"
|
|
4312
|
+
].includes(shape);
|
|
4313
|
+
const shouldMirror = mirrorAxis !== null && isBasicShape && size > adjustedMaxSize * 0.2;
|
|
4314
|
+
const shapeConfig = {
|
|
3146
4315
|
fillColor: transparentFill,
|
|
3147
4316
|
strokeColor: strokeColor,
|
|
3148
4317
|
strokeWidth: strokeWidth,
|
|
@@ -3154,22 +4323,28 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3154
4323
|
gradientFillEnd: gradientEnd,
|
|
3155
4324
|
renderStyle: finalRenderStyle,
|
|
3156
4325
|
rng: rng
|
|
4326
|
+
};
|
|
4327
|
+
if (shouldMirror) (0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4328
|
+
...shapeConfig,
|
|
4329
|
+
mirrorAxis: mirrorAxis,
|
|
4330
|
+
mirrorGap: size * (0.1 + rng() * 0.3)
|
|
3157
4331
|
});
|
|
4332
|
+
else (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
|
|
3158
4333
|
shapePositions.push({
|
|
3159
|
-
x:
|
|
3160
|
-
y:
|
|
4334
|
+
x: finalX,
|
|
4335
|
+
y: finalY,
|
|
3161
4336
|
size: size,
|
|
3162
4337
|
shape: shape
|
|
3163
4338
|
});
|
|
3164
|
-
// ──
|
|
4339
|
+
// ── 5c. Size echo — large shapes spawn trailing smaller copies ──
|
|
3165
4340
|
if (size > adjustedMaxSize * 0.5 && rng() < 0.2) {
|
|
3166
4341
|
const echoCount = 2 + Math.floor(rng() * 2);
|
|
3167
4342
|
const echoAngle = rng() * Math.PI * 2;
|
|
3168
4343
|
for(let e = 0; e < echoCount; e++){
|
|
3169
4344
|
const echoScale = 0.3 - e * 0.08;
|
|
3170
4345
|
const echoDist = size * (0.6 + e * 0.4);
|
|
3171
|
-
const echoX =
|
|
3172
|
-
const echoY =
|
|
4346
|
+
const echoX = finalX + Math.cos(echoAngle) * echoDist;
|
|
4347
|
+
const echoY = finalY + Math.sin(echoAngle) * echoDist;
|
|
3173
4348
|
const echoSize = size * Math.max(0.1, echoScale);
|
|
3174
4349
|
if (echoX < 0 || echoX > width || echoY < 0 || echoY > height) continue;
|
|
3175
4350
|
ctx.globalAlpha = layerOpacity * (0.4 - e * 0.1);
|
|
@@ -3191,7 +4366,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3191
4366
|
});
|
|
3192
4367
|
}
|
|
3193
4368
|
}
|
|
3194
|
-
// ──
|
|
4369
|
+
// ── 5d. Recursive nesting ──────────────────────────────────
|
|
3195
4370
|
if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
|
|
3196
4371
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
3197
4372
|
for(let n = 0; n < innerCount; n++){
|
|
@@ -3204,7 +4379,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3204
4379
|
const innerRot = rng() * 360;
|
|
3205
4380
|
const innerFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1), 0.3 + rng() * 0.4);
|
|
3206
4381
|
ctx.globalAlpha = layerOpacity * 0.7;
|
|
3207
|
-
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, innerShape,
|
|
4382
|
+
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, innerShape, finalX + innerOffX, finalY + innerOffY, {
|
|
3208
4383
|
fillColor: innerFill,
|
|
3209
4384
|
strokeColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(strokeColor, 0.5),
|
|
3210
4385
|
strokeWidth: strokeWidth * 0.6,
|
|
@@ -3216,6 +4391,41 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3216
4391
|
});
|
|
3217
4392
|
}
|
|
3218
4393
|
}
|
|
4394
|
+
// ── 5e. Shape constellations — pre-composed groups ─────────
|
|
4395
|
+
if (size > adjustedMaxSize * 0.35 && rng() < 0.12) {
|
|
4396
|
+
const constellation = $b623126c6e9cbb71$var$CONSTELLATIONS[Math.floor(rng() * $b623126c6e9cbb71$var$CONSTELLATIONS.length)];
|
|
4397
|
+
const members = constellation.build(rng, size);
|
|
4398
|
+
const groupRotation = rng() * Math.PI * 2;
|
|
4399
|
+
const cosR = Math.cos(groupRotation);
|
|
4400
|
+
const sinR = Math.sin(groupRotation);
|
|
4401
|
+
for (const member of members){
|
|
4402
|
+
// Rotate the group offset by the group rotation
|
|
4403
|
+
const mx = finalX + member.dx * cosR - member.dy * sinR;
|
|
4404
|
+
const my = finalY + member.dx * sinR + member.dy * cosR;
|
|
4405
|
+
if (mx < 0 || mx > width || my < 0 || my > height) continue;
|
|
4406
|
+
const memberFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 8, 0.06), fillAlpha * 0.8);
|
|
4407
|
+
const memberStroke = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
|
|
4408
|
+
ctx.globalAlpha = layerOpacity * 0.6;
|
|
4409
|
+
// Use the member's shape if available, otherwise fall back to palette
|
|
4410
|
+
const memberShape = shapeNames.includes(member.shape) ? member.shape : (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, member.size / adjustedMaxSize);
|
|
4411
|
+
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, memberShape, mx, my, {
|
|
4412
|
+
fillColor: memberFill,
|
|
4413
|
+
strokeColor: memberStroke,
|
|
4414
|
+
strokeWidth: strokeWidth * 0.7,
|
|
4415
|
+
size: member.size,
|
|
4416
|
+
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
4417
|
+
proportionType: "GOLDEN_RATIO",
|
|
4418
|
+
renderStyle: (0, $24064302523652b1$export$ab873bb6fb56c1a8)(memberShape, layerRenderStyle, rng),
|
|
4419
|
+
rng: rng
|
|
4420
|
+
});
|
|
4421
|
+
shapePositions.push({
|
|
4422
|
+
x: mx,
|
|
4423
|
+
y: my,
|
|
4424
|
+
size: member.size,
|
|
4425
|
+
shape: memberShape
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
3219
4429
|
}
|
|
3220
4430
|
}
|
|
3221
4431
|
// Reset blend mode for post-processing passes
|