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/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); // 5-9 lobes
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; // radius varies 50-100%
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
- const midX = (curr.x + next.x) / 2;
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); // 3-12 sides
1339
- const jitterAmount = 0.1 + rng() * 0.4; // 10-50% vertex displacement
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
- // Frequency ratios small integers produce recognizable patterns
1355
- const freqA = 1 + Math.floor(rng() * 5); // 1-5
1356
- const freqB = 1 + Math.floor(rng() * 5); // 1-5
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; // 0.2-0.8
1394
- const d = 0.3 + rng() * 0.7; // 0.3-1.0
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; // cap at 10 rotations
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); // 2-5 rings
1419
- const freq = 3 + Math.floor(rng() * 12); // 3-14 waves per ring
1420
- const amp = 0.05 + rng() * 0.15; // 5-20% of radius
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); // 2-7 petal parameter
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
- ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5);
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
- (0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
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: x,
3151
- y: y,
4325
+ x: finalX,
4326
+ y: finalY,
3152
4327
  size: size,
3153
4328
  shape: shape
3154
4329
  });
3155
- // ── 5b. Size echo — large shapes spawn trailing smaller copies ──
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 = x + Math.cos(echoAngle) * echoDist;
3163
- const echoY = y + Math.sin(echoAngle) * echoDist;
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
- // ── 5c. Recursive nesting ──────────────────────────────────
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, x + innerOffX, y + innerOffY, {
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