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