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