git-hash-art 0.8.0 → 0.10.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/module.js CHANGED
@@ -527,6 +527,18 @@ function $9d614e7d77fc2947$export$6d1620b367f86f7a(rng) {
527
527
  intensity: intensity
528
528
  };
529
529
  }
530
+ function $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(hex, degrees) {
531
+ const [h, s, l] = $9d614e7d77fc2947$var$hexToHsl(hex);
532
+ return $9d614e7d77fc2947$var$hslToHex((h + degrees + 360) % 360, s, l);
533
+ }
534
+ function $9d614e7d77fc2947$export$703ba40a4347f77a(base, layerRatio, hueShiftPerLayer) {
535
+ const shift = layerRatio * hueShiftPerLayer;
536
+ return {
537
+ dominant: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.dominant, shift),
538
+ secondary: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.secondary, shift * 0.7),
539
+ accent: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5)
540
+ };
541
+ }
530
542
 
531
543
 
532
544
 
@@ -1317,35 +1329,32 @@ const $d63629e16208c310$export$c2fc138f94dd4b2a = {
1317
1329
  */ const $a7241acce45d5513$export$580f80cfb9de73bc = (ctx, size, config)=>{
1318
1330
  const rng = config?.rng ?? Math.random;
1319
1331
  const r = size / 2;
1320
- const numPoints = 5 + Math.floor(rng() * 5); // 5-9 lobes
1332
+ const numPoints = 5 + Math.floor(rng() * 5);
1321
1333
  const points = [];
1322
1334
  for(let i = 0; i < numPoints; i++){
1323
1335
  const angle = i / numPoints * Math.PI * 2;
1324
- const jitter = 0.5 + rng() * 0.5; // radius varies 50-100%
1336
+ const jitter = 0.5 + rng() * 0.5;
1325
1337
  points.push({
1326
1338
  x: Math.cos(angle) * r * jitter,
1327
1339
  y: Math.sin(angle) * r * jitter
1328
1340
  });
1329
1341
  }
1330
1342
  ctx.beginPath();
1331
- // Start at midpoint between last and first point
1332
1343
  const last = points[points.length - 1];
1333
1344
  const first = points[0];
1334
1345
  ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
1335
1346
  for(let i = 0; i < numPoints; i++){
1336
1347
  const curr = points[i];
1337
1348
  const next = points[(i + 1) % numPoints];
1338
- const midX = (curr.x + next.x) / 2;
1339
- const midY = (curr.y + next.y) / 2;
1340
- ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
1349
+ ctx.quadraticCurveTo(curr.x, curr.y, (curr.x + next.x) / 2, (curr.y + next.y) / 2);
1341
1350
  }
1342
1351
  ctx.closePath();
1343
1352
  };
1344
1353
  const $a7241acce45d5513$export$7a6094023f0902a6 = (ctx, size, config)=>{
1345
1354
  const rng = config?.rng ?? Math.random;
1346
1355
  const r = size / 2;
1347
- const sides = 3 + Math.floor(rng() * 10); // 3-12 sides
1348
- const jitterAmount = 0.1 + rng() * 0.4; // 10-50% vertex displacement
1356
+ const sides = 3 + Math.floor(rng() * 10);
1357
+ const jitterAmount = 0.1 + rng() * 0.4;
1349
1358
  ctx.beginPath();
1350
1359
  for(let i = 0; i < sides; i++){
1351
1360
  const angle = i / sides * Math.PI * 2 - Math.PI / 2;
@@ -1360,10 +1369,9 @@ const $a7241acce45d5513$export$7a6094023f0902a6 = (ctx, size, config)=>{
1360
1369
  const $a7241acce45d5513$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
1361
1370
  const rng = config?.rng ?? Math.random;
1362
1371
  const r = size / 2;
1363
- // Frequency ratios small integers produce recognizable patterns
1364
- const freqA = 1 + Math.floor(rng() * 5); // 1-5
1365
- const freqB = 1 + Math.floor(rng() * 5); // 1-5
1366
- const phase = rng() * Math.PI; // phase offset
1372
+ const freqA = 1 + Math.floor(rng() * 5);
1373
+ const freqB = 1 + Math.floor(rng() * 5);
1374
+ const phase = rng() * Math.PI;
1367
1375
  const steps = 120;
1368
1376
  ctx.beginPath();
1369
1377
  for(let i = 0; i <= steps; i++){
@@ -1378,7 +1386,6 @@ const $a7241acce45d5513$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
1378
1386
  const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
1379
1387
  const rng = config?.rng ?? Math.random;
1380
1388
  const r = size / 2;
1381
- // Exponent range: 0.3 (spiky astroid) to 5 (rounded rectangle)
1382
1389
  const n = 0.3 + rng() * 4.7;
1383
1390
  const steps = 120;
1384
1391
  ctx.beginPath();
@@ -1386,7 +1393,6 @@ const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
1386
1393
  const t = i / steps * Math.PI * 2;
1387
1394
  const cosT = Math.cos(t);
1388
1395
  const sinT = Math.sin(t);
1389
- // Superellipse parametric form
1390
1396
  const x = Math.sign(cosT) * Math.pow(Math.abs(cosT), 2 / n) * r;
1391
1397
  const y = Math.sign(sinT) * Math.pow(Math.abs(sinT), 2 / n) * r;
1392
1398
  if (i === 0) ctx.moveTo(x, y);
@@ -1397,11 +1403,9 @@ const $a7241acce45d5513$export$1db9219b4f34658c = (ctx, size, config)=>{
1397
1403
  const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
1398
1404
  const rng = config?.rng ?? Math.random;
1399
1405
  const scale = size / 2;
1400
- // R = outer radius, r = inner radius, d = pen distance from inner center
1401
1406
  const R = 1;
1402
- const r = 0.2 + rng() * 0.6; // 0.2-0.8
1403
- const d = 0.3 + rng() * 0.7; // 0.3-1.0
1404
- // Number of full rotations needed to close the curve
1407
+ const r = 0.2 + rng() * 0.6;
1408
+ const d = 0.3 + rng() * 0.7;
1405
1409
  const gcd = (a, b)=>{
1406
1410
  const ai = Math.round(a * 1000);
1407
1411
  const bi = Math.round(b * 1000);
@@ -1409,7 +1413,7 @@ const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
1409
1413
  return g(ai, bi) / 1000;
1410
1414
  };
1411
1415
  const period = r / gcd(R, r);
1412
- const maxT = Math.min(period, 10) * Math.PI * 2; // cap at 10 rotations
1416
+ const maxT = Math.min(period, 10) * Math.PI * 2;
1413
1417
  const steps = Math.min(600, Math.floor(maxT * 20));
1414
1418
  ctx.beginPath();
1415
1419
  for(let i = 0; i <= steps; i++){
@@ -1424,9 +1428,9 @@ const $a7241acce45d5513$export$b027c64d22b01985 = (ctx, size, config)=>{
1424
1428
  const $a7241acce45d5513$export$7608ccd03bfb705d = (ctx, size, config)=>{
1425
1429
  const rng = config?.rng ?? Math.random;
1426
1430
  const r = size / 2;
1427
- const rings = 2 + Math.floor(rng() * 4); // 2-5 rings
1428
- const freq = 3 + Math.floor(rng() * 12); // 3-14 waves per ring
1429
- const amp = 0.05 + rng() * 0.15; // 5-20% of radius
1431
+ const rings = 2 + Math.floor(rng() * 4);
1432
+ const freq = 3 + Math.floor(rng() * 12);
1433
+ const amp = 0.05 + rng() * 0.15;
1430
1434
  ctx.beginPath();
1431
1435
  for(let ring = 0; ring < rings; ring++){
1432
1436
  const baseR = r * (0.3 + ring / rings * 0.7);
@@ -1444,7 +1448,7 @@ const $a7241acce45d5513$export$7608ccd03bfb705d = (ctx, size, config)=>{
1444
1448
  const $a7241acce45d5513$export$11a377e7498bb523 = (ctx, size, config)=>{
1445
1449
  const rng = config?.rng ?? Math.random;
1446
1450
  const r = size / 2;
1447
- const k = 2 + Math.floor(rng() * 6); // 2-7 petal parameter
1451
+ const k = 2 + Math.floor(rng() * 6);
1448
1452
  const steps = 200;
1449
1453
  ctx.beginPath();
1450
1454
  for(let i = 0; i <= steps; i++){
@@ -1457,6 +1461,308 @@ const $a7241acce45d5513$export$11a377e7498bb523 = (ctx, size, config)=>{
1457
1461
  }
1458
1462
  ctx.closePath();
1459
1463
  };
1464
+ const $a7241acce45d5513$export$76b6526575ea179b = (ctx, size, config)=>{
1465
+ const rng = config?.rng ?? Math.random;
1466
+ const r = size / 2;
1467
+ const shardCount = 4 + Math.floor(rng() * 5); // 4-8 shards
1468
+ ctx.beginPath();
1469
+ for(let s = 0; s < shardCount; s++){
1470
+ const baseAngle = s / shardCount * Math.PI * 2 + (rng() - 0.5) * 0.3;
1471
+ const dist = r * (0.15 + rng() * 0.35);
1472
+ const cx = Math.cos(baseAngle) * dist;
1473
+ const cy = Math.sin(baseAngle) * dist;
1474
+ const shardSize = r * (0.2 + rng() * 0.4);
1475
+ const verts = 3 + Math.floor(rng() * 3); // 3-5 vertices per shard
1476
+ const shardAngleOffset = rng() * Math.PI * 2;
1477
+ for(let v = 0; v < verts; v++){
1478
+ const angle = shardAngleOffset + v / verts * Math.PI * 2;
1479
+ // Elongate shards along their radial direction
1480
+ const stretch = v % 2 === 0 ? 1.0 : 0.3 + rng() * 0.4;
1481
+ const px = cx + Math.cos(angle) * shardSize * stretch;
1482
+ const py = cy + Math.sin(angle) * shardSize * stretch;
1483
+ if (v === 0) ctx.moveTo(px, py);
1484
+ else ctx.lineTo(px, py);
1485
+ }
1486
+ ctx.closePath();
1487
+ }
1488
+ };
1489
+ const $a7241acce45d5513$export$ed9ff98da5b05073 = (ctx, size, config)=>{
1490
+ const rng = config?.rng ?? Math.random;
1491
+ const r = size / 2;
1492
+ const edgeCount = 5 + Math.floor(rng() * 4); // 5-8 edges
1493
+ const points = [];
1494
+ // Generate edge midpoints at varying distances
1495
+ for(let i = 0; i < edgeCount; i++){
1496
+ const angle = i / edgeCount * Math.PI * 2 + (rng() - 0.5) * 0.4;
1497
+ const dist = r * (0.6 + rng() * 0.4);
1498
+ points.push({
1499
+ angle: angle,
1500
+ x: Math.cos(angle) * dist,
1501
+ y: Math.sin(angle) * dist
1502
+ });
1503
+ }
1504
+ // Sort by angle for proper winding
1505
+ points.sort((a, b)=>a.angle - b.angle);
1506
+ ctx.beginPath();
1507
+ ctx.moveTo(points[0].x, points[0].y);
1508
+ for(let i = 1; i < points.length; i++)ctx.lineTo(points[i].x, points[i].y);
1509
+ ctx.closePath();
1510
+ };
1511
+ const $a7241acce45d5513$export$e0452d9a794fe7e5 = (ctx, size, config)=>{
1512
+ const rng = config?.rng ?? Math.random;
1513
+ const r = size / 2;
1514
+ const biteSize = 0.6 + rng() * 0.3; // 60-90% of radius
1515
+ const biteOffset = r * (0.3 + rng() * 0.4);
1516
+ const biteAngle = rng() * Math.PI * 2;
1517
+ // Outer circle
1518
+ ctx.beginPath();
1519
+ ctx.arc(0, 0, r, 0, Math.PI * 2);
1520
+ // Subtract inner circle using even-odd rule
1521
+ const bx = Math.cos(biteAngle) * biteOffset;
1522
+ const by = Math.sin(biteAngle) * biteOffset;
1523
+ // Draw inner circle counter-clockwise for subtraction
1524
+ ctx.moveTo(bx + r * biteSize, by);
1525
+ ctx.arc(bx, by, r * biteSize, 0, Math.PI * 2, true);
1526
+ };
1527
+ const $a7241acce45d5513$export$38bfe5eb52137e01 = (ctx, size, config)=>{
1528
+ const rng = config?.rng ?? Math.random;
1529
+ const r = size / 2;
1530
+ const segments = 12 + Math.floor(rng() * 8);
1531
+ const startAngle = rng() * Math.PI * 2;
1532
+ const curvature = (rng() - 0.5) * 0.4;
1533
+ // Build spine points
1534
+ const spine = [];
1535
+ let angle = startAngle;
1536
+ let px = 0, py = 0;
1537
+ for(let i = 0; i <= segments; i++){
1538
+ spine.push({
1539
+ x: px,
1540
+ y: py
1541
+ });
1542
+ const stepLen = r / segments * (1.5 + rng() * 0.5);
1543
+ angle += curvature + (rng() - 0.5) * 0.6;
1544
+ px += Math.cos(angle) * stepLen;
1545
+ py += Math.sin(angle) * stepLen;
1546
+ }
1547
+ // Build tapered outline by offsetting perpendicular to spine
1548
+ ctx.beginPath();
1549
+ const leftSide = [];
1550
+ const rightSide = [];
1551
+ for(let i = 0; i < spine.length; i++){
1552
+ const t = i / (spine.length - 1);
1553
+ const width = r * 0.12 * (1 - t * 0.9); // taper from thick to thin
1554
+ const next = spine[Math.min(i + 1, spine.length - 1)];
1555
+ const dx = next.x - spine[i].x;
1556
+ const dy = next.y - spine[i].y;
1557
+ const len = Math.hypot(dx, dy) || 1;
1558
+ const nx = -dy / len;
1559
+ const ny = dx / len;
1560
+ leftSide.push({
1561
+ x: spine[i].x + nx * width,
1562
+ y: spine[i].y + ny * width
1563
+ });
1564
+ rightSide.push({
1565
+ x: spine[i].x - nx * width,
1566
+ y: spine[i].y - ny * width
1567
+ });
1568
+ }
1569
+ ctx.moveTo(leftSide[0].x, leftSide[0].y);
1570
+ for(let i = 1; i < leftSide.length; i++)ctx.lineTo(leftSide[i].x, leftSide[i].y);
1571
+ for(let i = rightSide.length - 1; i >= 0; i--)ctx.lineTo(rightSide[i].x, rightSide[i].y);
1572
+ ctx.closePath();
1573
+ };
1574
+ const $a7241acce45d5513$export$105caa8cfd63c422 = (ctx, size, config)=>{
1575
+ const rng = config?.rng ?? Math.random;
1576
+ const r = size / 2;
1577
+ const lobeCount = 4 + Math.floor(rng() * 4); // 4-7 lobes
1578
+ const spineAngle = rng() * Math.PI * 2;
1579
+ const spineLen = r * 0.6;
1580
+ ctx.beginPath();
1581
+ for(let i = 0; i < lobeCount; i++){
1582
+ const t = i / (lobeCount - 1) - 0.5; // -0.5 to 0.5
1583
+ const sx = Math.cos(spineAngle) * spineLen * t;
1584
+ const sy = Math.sin(spineAngle) * spineLen * t;
1585
+ // Offset perpendicular for cloud shape
1586
+ const perpAngle = spineAngle + Math.PI / 2;
1587
+ const perpOff = (rng() - 0.3) * r * 0.3;
1588
+ const cx = sx + Math.cos(perpAngle) * perpOff;
1589
+ const cy = sy + Math.sin(perpAngle) * perpOff;
1590
+ const lobeR = r * (0.25 + rng() * 0.2);
1591
+ ctx.moveTo(cx + lobeR, cy);
1592
+ ctx.arc(cx, cy, lobeR, 0, Math.PI * 2);
1593
+ }
1594
+ };
1595
+ const $a7241acce45d5513$export$e181e5bd3c539569 = (ctx, size, config)=>{
1596
+ const rng = config?.rng ?? Math.random;
1597
+ const r = size / 2;
1598
+ const spikeCount = 8 + Math.floor(rng() * 8); // 8-15 spikes
1599
+ const points = [];
1600
+ for(let i = 0; i < spikeCount; i++){
1601
+ const angle = i / spikeCount * Math.PI * 2;
1602
+ const isSpike = i % 2 === 0;
1603
+ const dist = isSpike ? r * (0.5 + rng() * 0.5 // spikes reach 50-100% of radius
1604
+ ) : r * (0.15 + rng() * 0.2); // valleys at 15-35%
1605
+ points.push({
1606
+ x: Math.cos(angle) * dist,
1607
+ y: Math.sin(angle) * dist
1608
+ });
1609
+ }
1610
+ ctx.beginPath();
1611
+ ctx.moveTo(points[0].x, points[0].y);
1612
+ for(let i = 0; i < points.length; i++){
1613
+ const curr = points[i];
1614
+ const next = points[(i + 1) % points.length];
1615
+ const cpx = (curr.x + next.x) / 2 + (rng() - 0.5) * r * 0.15;
1616
+ const cpy = (curr.y + next.y) / 2 + (rng() - 0.5) * r * 0.15;
1617
+ ctx.quadraticCurveTo(cpx, cpy, next.x, next.y);
1618
+ }
1619
+ ctx.closePath();
1620
+ };
1621
+ const $a7241acce45d5513$export$155b4780b4c6bb7b = (ctx, size, config)=>{
1622
+ const rng = config?.rng ?? Math.random;
1623
+ const r = size / 2;
1624
+ const subdivisions = 1 + Math.floor(rng() * 3); // 1-3
1625
+ // Start with icosahedron vertices projected to 2D
1626
+ const baseVerts = 6 + subdivisions * 4;
1627
+ const points = [];
1628
+ for(let i = 0; i < baseVerts; i++){
1629
+ const angle = i / baseVerts * Math.PI * 2;
1630
+ const ring = i % 2 === 0 ? 1.0 : 0.5 + rng() * 0.3;
1631
+ points.push({
1632
+ x: Math.cos(angle) * r * ring,
1633
+ y: Math.sin(angle) * r * ring
1634
+ });
1635
+ }
1636
+ ctx.beginPath();
1637
+ // Draw triangulated mesh — connect each point to neighbors and center
1638
+ for(let i = 0; i < points.length; i++){
1639
+ const next = points[(i + 1) % points.length];
1640
+ ctx.moveTo(points[i].x, points[i].y);
1641
+ ctx.lineTo(next.x, next.y);
1642
+ // Connect to center
1643
+ ctx.moveTo(points[i].x, points[i].y);
1644
+ ctx.lineTo(0, 0);
1645
+ // Cross-connect to create triangulation
1646
+ if (i % 2 === 0 && i + 2 < points.length) {
1647
+ ctx.moveTo(points[i].x, points[i].y);
1648
+ ctx.lineTo(points[i + 2].x, points[i + 2].y);
1649
+ }
1650
+ }
1651
+ // Outer ring
1652
+ ctx.moveTo(points[0].x, points[0].y);
1653
+ for(let i = 1; i < points.length; i++)ctx.lineTo(points[i].x, points[i].y);
1654
+ ctx.closePath();
1655
+ };
1656
+ const $a7241acce45d5513$export$9a7e648f11155172 = (ctx, size, config)=>{
1657
+ const rng = config?.rng ?? Math.random;
1658
+ const r = size / 2;
1659
+ const phi = (1 + Math.sqrt(5)) / 2; // golden ratio
1660
+ const isKite = rng() < 0.5;
1661
+ ctx.beginPath();
1662
+ if (isKite) {
1663
+ // Kite: two golden triangles joined at base
1664
+ const topY = -r;
1665
+ const bottomY = r * (1 / phi);
1666
+ const midY = r * (1 / phi - 1) * 0.3;
1667
+ const wingX = r * 0.6;
1668
+ ctx.moveTo(0, topY);
1669
+ ctx.lineTo(wingX, midY);
1670
+ ctx.lineTo(0, bottomY);
1671
+ ctx.lineTo(-wingX, midY);
1672
+ } else {
1673
+ // Dart: concave quadrilateral
1674
+ const topY = -r;
1675
+ const bottomY = r * 0.3;
1676
+ const midY = -r * 0.1;
1677
+ const wingX = r * 0.5;
1678
+ ctx.moveTo(0, topY);
1679
+ ctx.lineTo(wingX, midY);
1680
+ ctx.lineTo(0, bottomY);
1681
+ ctx.lineTo(-wingX, midY);
1682
+ }
1683
+ ctx.closePath();
1684
+ };
1685
+ const $a7241acce45d5513$export$1fc0aedbabd73399 = (ctx, size, config)=>{
1686
+ const r = size / 2;
1687
+ // Vertices of equilateral triangle
1688
+ const verts = [];
1689
+ for(let i = 0; i < 3; i++){
1690
+ const angle = i / 3 * Math.PI * 2 - Math.PI / 2;
1691
+ verts.push({
1692
+ x: Math.cos(angle) * r * 0.7,
1693
+ y: Math.sin(angle) * r * 0.7
1694
+ });
1695
+ }
1696
+ // Side length = distance between vertices
1697
+ const sideLen = Math.hypot(verts[1].x - verts[0].x, verts[1].y - verts[0].y);
1698
+ ctx.beginPath();
1699
+ for(let i = 0; i < 3; i++){
1700
+ const from = verts[(i + 1) % 3];
1701
+ const to = verts[(i + 2) % 3];
1702
+ const center = verts[i];
1703
+ const startAngle = Math.atan2(from.y - center.y, from.x - center.x);
1704
+ const endAngle = Math.atan2(to.y - center.y, to.x - center.x);
1705
+ if (i === 0) ctx.moveTo(from.x, from.y);
1706
+ ctx.arc(center.x, center.y, sideLen, startAngle, endAngle);
1707
+ }
1708
+ ctx.closePath();
1709
+ };
1710
+ const $a7241acce45d5513$export$ef7b5e0c19a21fd1 = (ctx, size, config)=>{
1711
+ const rng = config?.rng ?? Math.random;
1712
+ const r = size / 2;
1713
+ const dotCount = 15 + Math.floor(rng() * 25); // 15-39 dots
1714
+ const clusterTightness = 0.3 + rng() * 0.5;
1715
+ ctx.beginPath();
1716
+ for(let i = 0; i < dotCount; i++){
1717
+ // Gaussian-ish distribution via Box-Muller approximation
1718
+ const u1 = Math.max(0.001, rng());
1719
+ const u2 = rng();
1720
+ const mag = Math.sqrt(-2 * Math.log(u1)) * clusterTightness;
1721
+ const angle = u2 * Math.PI * 2;
1722
+ const dx = Math.cos(angle) * mag * r;
1723
+ const dy = Math.sin(angle) * mag * r;
1724
+ const dotR = r * (0.02 + rng() * 0.04);
1725
+ ctx.moveTo(dx + dotR, dy);
1726
+ ctx.arc(dx, dy, dotR, 0, Math.PI * 2);
1727
+ }
1728
+ };
1729
+ const $a7241acce45d5513$export$f15df8ab60dfcc9a = (ctx, size, config)=>{
1730
+ const rng = config?.rng ?? Math.random;
1731
+ const r = size / 2;
1732
+ const angle1 = rng() * Math.PI;
1733
+ const angle2 = angle1 + Math.PI / 2 + (rng() - 0.5) * 0.3;
1734
+ const spacing = r * (0.08 + rng() * 0.08);
1735
+ const hasCross = rng() < 0.6;
1736
+ // Draw bounding shape (ellipse)
1737
+ const rx = r * (0.7 + rng() * 0.3);
1738
+ const ry = r * (0.5 + rng() * 0.3);
1739
+ // Outer boundary
1740
+ ctx.beginPath();
1741
+ ctx.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2);
1742
+ // Hatch lines clipped to the ellipse
1743
+ const cos1 = Math.cos(angle1);
1744
+ const sin1 = Math.sin(angle1);
1745
+ for(let d = -r; d <= r; d += spacing){
1746
+ const lx1 = d * cos1 - r * sin1;
1747
+ const ly1 = d * sin1 + r * cos1;
1748
+ const lx2 = d * cos1 + r * sin1;
1749
+ const ly2 = d * sin1 - r * cos1;
1750
+ ctx.moveTo(lx1, ly1);
1751
+ ctx.lineTo(lx2, ly2);
1752
+ }
1753
+ if (hasCross) {
1754
+ const cos2 = Math.cos(angle2);
1755
+ const sin2 = Math.sin(angle2);
1756
+ for(let d = -r; d <= r; d += spacing * 1.3){
1757
+ const lx1 = d * cos2 - r * sin2;
1758
+ const ly1 = d * sin2 + r * cos2;
1759
+ const lx2 = d * cos2 + r * sin2;
1760
+ const ly2 = d * sin2 - r * cos2;
1761
+ ctx.moveTo(lx1, ly1);
1762
+ ctx.lineTo(lx2, ly2);
1763
+ }
1764
+ }
1765
+ };
1460
1766
  const $a7241acce45d5513$export$40cfb4c637f2fbb5 = {
1461
1767
  blob: $a7241acce45d5513$export$580f80cfb9de73bc,
1462
1768
  ngon: $a7241acce45d5513$export$7a6094023f0902a6,
@@ -1464,7 +1770,18 @@ const $a7241acce45d5513$export$40cfb4c637f2fbb5 = {
1464
1770
  superellipse: $a7241acce45d5513$export$1db9219b4f34658c,
1465
1771
  spirograph: $a7241acce45d5513$export$b027c64d22b01985,
1466
1772
  waveRing: $a7241acce45d5513$export$7608ccd03bfb705d,
1467
- rose: $a7241acce45d5513$export$11a377e7498bb523
1773
+ rose: $a7241acce45d5513$export$11a377e7498bb523,
1774
+ shardField: $a7241acce45d5513$export$76b6526575ea179b,
1775
+ voronoiCell: $a7241acce45d5513$export$ed9ff98da5b05073,
1776
+ crescent: $a7241acce45d5513$export$e0452d9a794fe7e5,
1777
+ tendril: $a7241acce45d5513$export$38bfe5eb52137e01,
1778
+ cloudForm: $a7241acce45d5513$export$105caa8cfd63c422,
1779
+ inkSplat: $a7241acce45d5513$export$e181e5bd3c539569,
1780
+ geodesicDome: $a7241acce45d5513$export$155b4780b4c6bb7b,
1781
+ penroseTile: $a7241acce45d5513$export$9a7e648f11155172,
1782
+ reuleauxTriangle: $a7241acce45d5513$export$1fc0aedbabd73399,
1783
+ dotCluster: $a7241acce45d5513$export$ef7b5e0c19a21fd1,
1784
+ crosshatchPatch: $a7241acce45d5513$export$f15df8ab60dfcc9a
1468
1785
  };
1469
1786
 
1470
1787
 
@@ -1499,7 +1816,14 @@ const $9beb8f41637c29fd$var$RENDER_STYLES = [
1499
1816
  "dashed",
1500
1817
  "watercolor",
1501
1818
  "hatched",
1502
- "incomplete"
1819
+ "incomplete",
1820
+ "stipple",
1821
+ "stencil",
1822
+ "noise-grain",
1823
+ "wood-grain",
1824
+ "marble-vein",
1825
+ "fabric-weave",
1826
+ "hand-drawn"
1503
1827
  ];
1504
1828
  function $9beb8f41637c29fd$export$9fd4e64b2acd410e(rng) {
1505
1829
  return $9beb8f41637c29fd$var$RENDER_STYLES[Math.floor(rng() * $9beb8f41637c29fd$var$RENDER_STYLES.length)];
@@ -1673,6 +1997,240 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
1673
1997
  ctx.lineDashOffset = 0;
1674
1998
  break;
1675
1999
  }
2000
+ case "stipple":
2001
+ {
2002
+ // Dot-fill texture — clip to shape, then scatter dots
2003
+ const savedAlphaS = ctx.globalAlpha;
2004
+ ctx.globalAlpha = savedAlphaS * 0.15;
2005
+ ctx.fill(); // ghost fill
2006
+ ctx.globalAlpha = savedAlphaS;
2007
+ ctx.save();
2008
+ ctx.clip();
2009
+ const dotSpacing = Math.max(2, size * 0.03);
2010
+ const extent = size * 0.55;
2011
+ ctx.globalAlpha = savedAlphaS * 0.7;
2012
+ for(let dx = -extent; dx <= extent; dx += dotSpacing)for(let dy = -extent; dy <= extent; dy += dotSpacing){
2013
+ // Jitter each dot position for organic feel
2014
+ const jx = rng ? (rng() - 0.5) * dotSpacing * 0.6 : 0;
2015
+ const jy = rng ? (rng() - 0.5) * dotSpacing * 0.6 : 0;
2016
+ const dotR = rng ? dotSpacing * (0.15 + rng() * 0.2) : dotSpacing * 0.2;
2017
+ ctx.beginPath();
2018
+ ctx.arc(dx + jx, dy + jy, dotR, 0, Math.PI * 2);
2019
+ ctx.fill();
2020
+ }
2021
+ ctx.restore();
2022
+ ctx.globalAlpha = savedAlphaS;
2023
+ // Outline
2024
+ ctx.globalAlpha *= 0.4;
2025
+ ctx.stroke();
2026
+ ctx.globalAlpha /= 0.4;
2027
+ break;
2028
+ }
2029
+ case "stencil":
2030
+ {
2031
+ // Negative-space cutout — fill a rectangle, then erase the shape
2032
+ const savedAlphaSt = ctx.globalAlpha;
2033
+ // Fill a bounding area with the stroke color
2034
+ ctx.globalAlpha = savedAlphaSt * 0.5;
2035
+ ctx.fillStyle = strokeColor;
2036
+ ctx.fillRect(-size * 0.6, -size * 0.6, size * 1.2, size * 1.2);
2037
+ // Cut out the shape using destination-out
2038
+ ctx.globalCompositeOperation = "destination-out";
2039
+ ctx.globalAlpha = 1;
2040
+ ctx.fill();
2041
+ ctx.globalCompositeOperation = "source-over";
2042
+ ctx.globalAlpha = savedAlphaSt;
2043
+ // Subtle outline of the cutout
2044
+ ctx.globalAlpha *= 0.3;
2045
+ ctx.stroke();
2046
+ ctx.globalAlpha /= 0.3;
2047
+ break;
2048
+ }
2049
+ case "noise-grain":
2050
+ {
2051
+ // Procedural noise grain texture clipped to shape boundary
2052
+ const savedAlphaN = ctx.globalAlpha;
2053
+ ctx.globalAlpha = savedAlphaN * 0.25;
2054
+ ctx.fill(); // base tint
2055
+ ctx.globalAlpha = savedAlphaN;
2056
+ ctx.save();
2057
+ ctx.clip();
2058
+ const grainSpacing = Math.max(1.5, size * 0.015);
2059
+ const extentN = size * 0.55;
2060
+ ctx.globalAlpha = savedAlphaN * 0.6;
2061
+ for(let gx = -extentN; gx <= extentN; gx += grainSpacing)for(let gy = -extentN; gy <= extentN; gy += grainSpacing){
2062
+ if (!rng) break;
2063
+ const jx = (rng() - 0.5) * grainSpacing * 1.2;
2064
+ const jy = (rng() - 0.5) * grainSpacing * 1.2;
2065
+ const brightness = rng() > 0.5 ? 255 : 0;
2066
+ const dotAlpha = 0.15 + rng() * 0.35;
2067
+ ctx.globalAlpha = savedAlphaN * dotAlpha;
2068
+ ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
2069
+ const dotSize = grainSpacing * (0.3 + rng() * 0.5);
2070
+ ctx.fillRect(gx + jx, gy + jy, dotSize, dotSize);
2071
+ }
2072
+ ctx.restore();
2073
+ ctx.fillStyle = fillColor;
2074
+ ctx.globalAlpha = savedAlphaN;
2075
+ ctx.globalAlpha *= 0.4;
2076
+ ctx.stroke();
2077
+ ctx.globalAlpha /= 0.4;
2078
+ break;
2079
+ }
2080
+ case "wood-grain":
2081
+ {
2082
+ // Parallel wavy lines simulating wood grain, clipped to shape
2083
+ const savedAlphaW = ctx.globalAlpha;
2084
+ ctx.globalAlpha = savedAlphaW * 0.2;
2085
+ ctx.fill(); // base tint
2086
+ ctx.globalAlpha = savedAlphaW;
2087
+ ctx.save();
2088
+ ctx.clip();
2089
+ const grainLineSpacing = Math.max(2, size * 0.035);
2090
+ const extentW = size * 0.55;
2091
+ const waveFreq = rng ? 3 + rng() * 5 : 5;
2092
+ const waveAmp = rng ? size * (0.01 + rng() * 0.03) : size * 0.02;
2093
+ const grainAngle = rng ? rng() * Math.PI : Math.PI * 0.25;
2094
+ ctx.lineWidth = Math.max(0.5, strokeWidth * 0.3);
2095
+ ctx.globalAlpha = savedAlphaW * 0.5;
2096
+ const cosG = Math.cos(grainAngle);
2097
+ const sinG = Math.sin(grainAngle);
2098
+ for(let d = -extentW; d <= extentW; d += grainLineSpacing){
2099
+ ctx.beginPath();
2100
+ for(let t = -extentW; t <= extentW; t += 2){
2101
+ const wave = Math.sin(t / extentW * waveFreq * Math.PI) * waveAmp;
2102
+ const px = t * cosG - (d + wave) * sinG;
2103
+ const py = t * sinG + (d + wave) * cosG;
2104
+ if (t === -extentW) ctx.moveTo(px, py);
2105
+ else ctx.lineTo(px, py);
2106
+ }
2107
+ ctx.stroke();
2108
+ }
2109
+ ctx.restore();
2110
+ ctx.globalAlpha = savedAlphaW;
2111
+ ctx.globalAlpha *= 0.35;
2112
+ ctx.stroke();
2113
+ ctx.globalAlpha /= 0.35;
2114
+ break;
2115
+ }
2116
+ case "marble-vein":
2117
+ {
2118
+ // Branching vein lines on a soft fill, clipped to shape
2119
+ const savedAlphaM = ctx.globalAlpha;
2120
+ ctx.globalAlpha = savedAlphaM * 0.35;
2121
+ ctx.fill(); // soft base
2122
+ ctx.globalAlpha = savedAlphaM;
2123
+ ctx.save();
2124
+ ctx.clip();
2125
+ const veinCount = rng ? 2 + Math.floor(rng() * 3) : 3;
2126
+ const extentM = size * 0.45;
2127
+ ctx.lineWidth = Math.max(0.5, strokeWidth * 0.5);
2128
+ ctx.globalAlpha = savedAlphaM * 0.4;
2129
+ for(let v = 0; v < veinCount; v++){
2130
+ const startX = rng ? (rng() - 0.5) * extentM * 2 : 0;
2131
+ const startY = rng ? -extentM + rng() * extentM * 0.5 : -extentM;
2132
+ let vx = startX;
2133
+ let vy = startY;
2134
+ const steps = 15 + (rng ? Math.floor(rng() * 15) : 10);
2135
+ const stepLen = size * 0.04;
2136
+ ctx.beginPath();
2137
+ ctx.moveTo(vx, vy);
2138
+ for(let s = 0; s < steps; s++){
2139
+ const drift = rng ? (rng() - 0.5) * stepLen * 1.5 : 0;
2140
+ vx += drift;
2141
+ vy += stepLen;
2142
+ ctx.lineTo(vx, vy);
2143
+ // Branch ~20% of the time
2144
+ if (rng && rng() < 0.2 && s > 2 && s < steps - 3) {
2145
+ const branchDir = rng() < 0.5 ? -1 : 1;
2146
+ let bx = vx;
2147
+ let by = vy;
2148
+ const bSteps = 3 + Math.floor(rng() * 5);
2149
+ ctx.moveTo(bx, by);
2150
+ for(let bs = 0; bs < bSteps; bs++){
2151
+ bx += branchDir * stepLen * (0.5 + rng() * 0.5);
2152
+ by += stepLen * 0.6;
2153
+ ctx.lineTo(bx, by);
2154
+ }
2155
+ ctx.moveTo(vx, vy); // return to main vein
2156
+ }
2157
+ }
2158
+ ctx.stroke();
2159
+ }
2160
+ ctx.restore();
2161
+ ctx.globalAlpha = savedAlphaM;
2162
+ ctx.globalAlpha *= 0.3;
2163
+ ctx.stroke();
2164
+ ctx.globalAlpha /= 0.3;
2165
+ break;
2166
+ }
2167
+ case "fabric-weave":
2168
+ {
2169
+ // Interlocking horizontal/vertical threads clipped to shape
2170
+ const savedAlphaF = ctx.globalAlpha;
2171
+ ctx.globalAlpha = savedAlphaF * 0.15;
2172
+ ctx.fill(); // ghost base
2173
+ ctx.globalAlpha = savedAlphaF;
2174
+ ctx.save();
2175
+ ctx.clip();
2176
+ const threadSpacing = Math.max(2, size * 0.04);
2177
+ const extentF = size * 0.55;
2178
+ ctx.lineWidth = Math.max(0.8, threadSpacing * 0.5);
2179
+ ctx.globalAlpha = savedAlphaF * 0.55;
2180
+ // Horizontal threads
2181
+ for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
2182
+ ctx.beginPath();
2183
+ ctx.moveTo(-extentF, y);
2184
+ ctx.lineTo(extentF, y);
2185
+ ctx.stroke();
2186
+ }
2187
+ // Vertical threads (offset by half spacing for weave effect)
2188
+ ctx.globalAlpha = savedAlphaF * 0.45;
2189
+ ctx.strokeStyle = fillColor;
2190
+ for(let x = -extentF; x <= extentF; x += threadSpacing * 2){
2191
+ ctx.beginPath();
2192
+ for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
2193
+ // Over-under: draw segment, skip segment
2194
+ ctx.moveTo(x, y);
2195
+ ctx.lineTo(x, y + threadSpacing);
2196
+ }
2197
+ ctx.stroke();
2198
+ }
2199
+ ctx.strokeStyle = strokeColor;
2200
+ ctx.restore();
2201
+ ctx.globalAlpha = savedAlphaF;
2202
+ ctx.globalAlpha *= 0.3;
2203
+ ctx.stroke();
2204
+ ctx.globalAlpha /= 0.3;
2205
+ break;
2206
+ }
2207
+ case "hand-drawn":
2208
+ {
2209
+ // Wobbly hand-drawn edge treatment — fill normally, then redraw
2210
+ // the outline with perturbed control points for a sketchy feel
2211
+ const savedAlphaHD = ctx.globalAlpha;
2212
+ ctx.globalAlpha = savedAlphaHD * 0.85;
2213
+ ctx.fill();
2214
+ ctx.globalAlpha = savedAlphaHD;
2215
+ // Draw 2-3 slightly offset wobbly strokes for a sketchy look
2216
+ const wobblePasses = 2 + (rng ? Math.floor(rng() * 2) : 0);
2217
+ ctx.lineWidth = strokeWidth * 0.8;
2218
+ for(let wp = 0; wp < wobblePasses; wp++){
2219
+ ctx.globalAlpha = savedAlphaHD * (0.4 - wp * 0.1);
2220
+ ctx.save();
2221
+ // Slight random offset per pass
2222
+ const wobbleX = rng ? (rng() - 0.5) * size * 0.02 : 0;
2223
+ const wobbleY = rng ? (rng() - 0.5) * size * 0.02 : 0;
2224
+ ctx.translate(wobbleX, wobbleY);
2225
+ // Slightly different scale per pass for edge variation
2226
+ const wobbleScale = 1 + (rng ? (rng() - 0.5) * 0.03 : 0);
2227
+ ctx.scale(wobbleScale, wobbleScale);
2228
+ ctx.stroke();
2229
+ ctx.restore();
2230
+ }
2231
+ ctx.globalAlpha = savedAlphaHD;
2232
+ break;
2233
+ }
1676
2234
  case "fill-and-stroke":
1677
2235
  default:
1678
2236
  ctx.fill();
@@ -1719,6 +2277,64 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
1719
2277
  });
1720
2278
  ctx.restore();
1721
2279
  }
2280
+ function $9beb8f41637c29fd$export$8bd8bbd1a8e53689(ctx, shape, x, y, config) {
2281
+ const { mirrorAxis: mirrorAxis = "horizontal", mirrorGap: mirrorGap = 0 } = config;
2282
+ // Draw the primary shape
2283
+ $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config);
2284
+ // Draw the mirrored copy
2285
+ ctx.save();
2286
+ const savedAlpha = ctx.globalAlpha;
2287
+ ctx.globalAlpha = savedAlpha * 0.7; // mirror is slightly softer
2288
+ switch(mirrorAxis){
2289
+ case "horizontal":
2290
+ // Reflect across vertical axis at shape position
2291
+ $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y + mirrorGap, {
2292
+ ...config,
2293
+ rotation: -(config.rotation || 0),
2294
+ size: config.size * 0.95
2295
+ });
2296
+ break;
2297
+ case "vertical":
2298
+ $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x + mirrorGap, y, {
2299
+ ...config,
2300
+ rotation: 180 - (config.rotation || 0),
2301
+ size: config.size * 0.95
2302
+ });
2303
+ break;
2304
+ case "diagonal":
2305
+ // Reflect across 45° axis
2306
+ $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x + mirrorGap * 0.7, y + mirrorGap * 0.7, {
2307
+ ...config,
2308
+ rotation: 90 - (config.rotation || 0),
2309
+ size: config.size * 0.9
2310
+ });
2311
+ break;
2312
+ case "radial-4":
2313
+ // Four-way radial mirror
2314
+ for(let i = 1; i < 4; i++){
2315
+ const angle = i / 4 * Math.PI * 2;
2316
+ const mx = x + Math.cos(angle) * mirrorGap;
2317
+ const my = y + Math.sin(angle) * mirrorGap;
2318
+ ctx.globalAlpha = savedAlpha * (0.7 - i * 0.1);
2319
+ $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, mx, my, {
2320
+ ...config,
2321
+ rotation: (config.rotation || 0) + i * 90,
2322
+ size: config.size * (0.95 - i * 0.05)
2323
+ });
2324
+ }
2325
+ break;
2326
+ }
2327
+ ctx.globalAlpha = savedAlpha;
2328
+ ctx.restore();
2329
+ }
2330
+ function $9beb8f41637c29fd$export$879206e23912d1a9(rng) {
2331
+ const roll = rng();
2332
+ if (roll < 0.60) return null;
2333
+ if (roll < 0.75) return "horizontal";
2334
+ if (roll < 0.87) return "vertical";
2335
+ if (roll < 0.95) return "diagonal";
2336
+ return "radial-4";
2337
+ }
1722
2338
 
1723
2339
 
1724
2340
 
@@ -1750,7 +2366,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
1750
2366
  bestStyles: [
1751
2367
  "fill-only",
1752
2368
  "watercolor",
1753
- "fill-and-stroke"
2369
+ "fill-and-stroke",
2370
+ "hand-drawn"
1754
2371
  ]
1755
2372
  },
1756
2373
  square: {
@@ -1787,7 +2404,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
1787
2404
  bestStyles: [
1788
2405
  "fill-and-stroke",
1789
2406
  "fill-only",
1790
- "watercolor"
2407
+ "watercolor",
2408
+ "hand-drawn"
1791
2409
  ]
1792
2410
  },
1793
2411
  hexagon: {
@@ -2167,7 +2785,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
2167
2785
  bestStyles: [
2168
2786
  "fill-only",
2169
2787
  "watercolor",
2170
- "fill-and-stroke"
2788
+ "fill-and-stroke",
2789
+ "hand-drawn"
2171
2790
  ]
2172
2791
  },
2173
2792
  ngon: {
@@ -2221,7 +2840,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
2221
2840
  bestStyles: [
2222
2841
  "fill-only",
2223
2842
  "watercolor",
2224
- "fill-and-stroke"
2843
+ "fill-and-stroke",
2844
+ "wood-grain"
2225
2845
  ]
2226
2846
  },
2227
2847
  spirograph: {
@@ -2277,84 +2897,355 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
2277
2897
  "fill-only",
2278
2898
  "watercolor"
2279
2899
  ]
2280
- }
2281
- };
2282
- function $24064302523652b1$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
2283
- const available = shapeNames.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]);
2284
- // Pick a seed shape — tier 1 shapes that are hero candidates
2285
- const heroPool = available.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s].tier === 1 && $24064302523652b1$export$4343b39fe47bd82c[s].heroCandidate);
2286
- const seedShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : available[Math.floor(rng() * available.length)];
2287
- const seedProfile = $24064302523652b1$export$4343b39fe47bd82c[seedShape];
2288
- // Primary: seed shape + its direct affinities (tier 1-2 only)
2289
- const primaryCandidates = [
2290
- seedShape,
2291
- ...seedProfile.affinities
2292
- ].filter((s)=>available.includes(s)).filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s].tier <= 2);
2293
- const primary = [
2294
- ...new Set(primaryCandidates)
2295
- ].slice(0, 5);
2296
- // Supporting: affinities of affinities, plus same-category shapes
2297
- const supportingSet = new Set();
2298
- for (const p of primary){
2299
- const profile = $24064302523652b1$export$4343b39fe47bd82c[p];
2300
- if (!profile) continue;
2301
- for (const aff of profile.affinities)if (available.includes(aff) && !primary.includes(aff)) supportingSet.add(aff);
2302
- }
2303
- // Add same-category tier 1-2 shapes
2304
- for (const s of available){
2305
- const p = $24064302523652b1$export$4343b39fe47bd82c[s];
2306
- if (p.category === seedProfile.category && p.tier <= 2 && !primary.includes(s)) supportingSet.add(s);
2307
- }
2308
- const supporting = [
2309
- ...supportingSet
2310
- ].slice(0, 6);
2311
- // Accents: tier 1 shapes from other categories for contrast
2312
- const usedCategories = new Set([
2313
- ...primary,
2314
- ...supporting
2315
- ].map((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]?.category));
2316
- const accentCandidates = available.filter((s)=>!primary.includes(s) && !supporting.includes(s) && $24064302523652b1$export$4343b39fe47bd82c[s].tier <= 2 && !usedCategories.has($24064302523652b1$export$4343b39fe47bd82c[s].category));
2317
- // Shuffle and take a few
2318
- const accents = [];
2319
- const shuffled = [
2320
- ...accentCandidates
2321
- ];
2322
- for(let i = shuffled.length - 1; i > 0; i--){
2323
- const j = Math.floor(rng() * (i + 1));
2324
- [shuffled[i], shuffled[j]] = [
2325
- shuffled[j],
2326
- shuffled[i]
2327
- ];
2328
- }
2329
- accents.push(...shuffled.slice(0, 3));
2330
- // For certain archetypes, bias the palette
2331
- if (archetypeName === "geometric-precision") // Remove blobs and organic shapes from primary
2332
- return {
2333
- primary: primary.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]?.category !== "procedural" || s === "ngon"),
2334
- supporting: supporting.filter((s)=>s !== "blob"),
2335
- accents: accents
2336
- };
2337
- if (archetypeName === "organic-flow") {
2338
- // Boost procedural/organic shapes
2339
- const organicBoost = available.filter((s)=>[
2340
- "blob",
2341
- "superellipse",
2342
- "waveRing",
2343
- "rose"
2344
- ].includes(s) && !primary.includes(s));
2345
- return {
2346
- primary: [
2347
- ...primary,
2348
- ...organicBoost.slice(0, 2)
2349
- ],
2350
- supporting: supporting,
2351
- accents: accents
2352
- };
2353
- }
2354
- return {
2355
- primary: primary,
2356
- supporting: supporting,
2357
- accents: accents
2900
+ },
2901
+ // ── New procedural shapes ─────────────────────────────────────
2902
+ shardField: {
2903
+ tier: 2,
2904
+ minSizeFraction: 0.1,
2905
+ maxSizeFraction: 0.7,
2906
+ affinities: [
2907
+ "voronoiCell",
2908
+ "diamond",
2909
+ "triangle",
2910
+ "penroseTile"
2911
+ ],
2912
+ category: "procedural",
2913
+ heroCandidate: false,
2914
+ bestStyles: [
2915
+ "fill-and-stroke",
2916
+ "stroke-only",
2917
+ "fill-only"
2918
+ ]
2919
+ },
2920
+ voronoiCell: {
2921
+ tier: 1,
2922
+ minSizeFraction: 0.08,
2923
+ maxSizeFraction: 0.9,
2924
+ affinities: [
2925
+ "shardField",
2926
+ "ngon",
2927
+ "superellipse",
2928
+ "blob"
2929
+ ],
2930
+ category: "procedural",
2931
+ heroCandidate: false,
2932
+ bestStyles: [
2933
+ "fill-and-stroke",
2934
+ "fill-only",
2935
+ "watercolor",
2936
+ "marble-vein"
2937
+ ]
2938
+ },
2939
+ crescent: {
2940
+ tier: 1,
2941
+ minSizeFraction: 0.1,
2942
+ maxSizeFraction: 1.0,
2943
+ affinities: [
2944
+ "circle",
2945
+ "blob",
2946
+ "cloudForm",
2947
+ "vesicaPiscis"
2948
+ ],
2949
+ category: "procedural",
2950
+ heroCandidate: true,
2951
+ bestStyles: [
2952
+ "fill-only",
2953
+ "watercolor",
2954
+ "fill-and-stroke"
2955
+ ]
2956
+ },
2957
+ tendril: {
2958
+ tier: 2,
2959
+ minSizeFraction: 0.1,
2960
+ maxSizeFraction: 0.8,
2961
+ affinities: [
2962
+ "blob",
2963
+ "inkSplat",
2964
+ "lissajous",
2965
+ "fibonacciSpiral"
2966
+ ],
2967
+ category: "procedural",
2968
+ heroCandidate: false,
2969
+ bestStyles: [
2970
+ "fill-only",
2971
+ "watercolor",
2972
+ "fill-and-stroke"
2973
+ ]
2974
+ },
2975
+ cloudForm: {
2976
+ tier: 1,
2977
+ minSizeFraction: 0.15,
2978
+ maxSizeFraction: 1.0,
2979
+ affinities: [
2980
+ "blob",
2981
+ "circle",
2982
+ "crescent",
2983
+ "superellipse"
2984
+ ],
2985
+ category: "procedural",
2986
+ heroCandidate: false,
2987
+ bestStyles: [
2988
+ "fill-only",
2989
+ "watercolor"
2990
+ ]
2991
+ },
2992
+ inkSplat: {
2993
+ tier: 2,
2994
+ minSizeFraction: 0.1,
2995
+ maxSizeFraction: 0.8,
2996
+ affinities: [
2997
+ "blob",
2998
+ "tendril",
2999
+ "shardField",
3000
+ "star"
3001
+ ],
3002
+ category: "procedural",
3003
+ heroCandidate: false,
3004
+ bestStyles: [
3005
+ "fill-only",
3006
+ "watercolor",
3007
+ "fill-and-stroke"
3008
+ ]
3009
+ },
3010
+ geodesicDome: {
3011
+ tier: 2,
3012
+ minSizeFraction: 0.2,
3013
+ maxSizeFraction: 0.9,
3014
+ affinities: [
3015
+ "metatronsCube",
3016
+ "platonicSolid",
3017
+ "hexagon",
3018
+ "triangle"
3019
+ ],
3020
+ category: "procedural",
3021
+ heroCandidate: true,
3022
+ bestStyles: [
3023
+ "stroke-only",
3024
+ "dashed",
3025
+ "double-stroke"
3026
+ ]
3027
+ },
3028
+ penroseTile: {
3029
+ tier: 2,
3030
+ minSizeFraction: 0.06,
3031
+ maxSizeFraction: 0.6,
3032
+ affinities: [
3033
+ "diamond",
3034
+ "triangle",
3035
+ "shardField",
3036
+ "voronoiCell"
3037
+ ],
3038
+ category: "procedural",
3039
+ heroCandidate: false,
3040
+ bestStyles: [
3041
+ "fill-and-stroke",
3042
+ "fill-only",
3043
+ "double-stroke"
3044
+ ]
3045
+ },
3046
+ reuleauxTriangle: {
3047
+ tier: 1,
3048
+ minSizeFraction: 0.08,
3049
+ maxSizeFraction: 1.0,
3050
+ affinities: [
3051
+ "triangle",
3052
+ "circle",
3053
+ "superellipse",
3054
+ "vesicaPiscis"
3055
+ ],
3056
+ category: "procedural",
3057
+ heroCandidate: true,
3058
+ bestStyles: [
3059
+ "fill-and-stroke",
3060
+ "fill-only",
3061
+ "watercolor"
3062
+ ]
3063
+ },
3064
+ dotCluster: {
3065
+ tier: 3,
3066
+ minSizeFraction: 0.05,
3067
+ maxSizeFraction: 0.5,
3068
+ affinities: [
3069
+ "cloudForm",
3070
+ "inkSplat",
3071
+ "blob"
3072
+ ],
3073
+ category: "procedural",
3074
+ heroCandidate: false,
3075
+ bestStyles: [
3076
+ "fill-only",
3077
+ "stipple"
3078
+ ]
3079
+ },
3080
+ crosshatchPatch: {
3081
+ tier: 3,
3082
+ minSizeFraction: 0.1,
3083
+ maxSizeFraction: 0.6,
3084
+ affinities: [
3085
+ "voronoiCell",
3086
+ "ngon",
3087
+ "superellipse"
3088
+ ],
3089
+ category: "procedural",
3090
+ heroCandidate: false,
3091
+ bestStyles: [
3092
+ "stroke-only",
3093
+ "hatched",
3094
+ "fabric-weave"
3095
+ ]
3096
+ }
3097
+ };
3098
+ function $24064302523652b1$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
3099
+ const available = shapeNames.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]);
3100
+ // Pick a seed shape — tier 1 shapes that are hero candidates
3101
+ const heroPool = available.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s].tier === 1 && $24064302523652b1$export$4343b39fe47bd82c[s].heroCandidate);
3102
+ const seedShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : available[Math.floor(rng() * available.length)];
3103
+ const seedProfile = $24064302523652b1$export$4343b39fe47bd82c[seedShape];
3104
+ // Primary: seed shape + its direct affinities (tier 1-2 only)
3105
+ const primaryCandidates = [
3106
+ seedShape,
3107
+ ...seedProfile.affinities
3108
+ ].filter((s)=>available.includes(s)).filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s].tier <= 2);
3109
+ const primary = [
3110
+ ...new Set(primaryCandidates)
3111
+ ].slice(0, 5);
3112
+ // Supporting: affinities of affinities, plus same-category shapes
3113
+ const supportingSet = new Set();
3114
+ for (const p of primary){
3115
+ const profile = $24064302523652b1$export$4343b39fe47bd82c[p];
3116
+ if (!profile) continue;
3117
+ for (const aff of profile.affinities)if (available.includes(aff) && !primary.includes(aff)) supportingSet.add(aff);
3118
+ }
3119
+ // Add same-category tier 1-2 shapes
3120
+ for (const s of available){
3121
+ const p = $24064302523652b1$export$4343b39fe47bd82c[s];
3122
+ if (p.category === seedProfile.category && p.tier <= 2 && !primary.includes(s)) supportingSet.add(s);
3123
+ }
3124
+ const supporting = [
3125
+ ...supportingSet
3126
+ ].slice(0, 6);
3127
+ // Accents: tier 1 shapes from other categories for contrast
3128
+ const usedCategories = new Set([
3129
+ ...primary,
3130
+ ...supporting
3131
+ ].map((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]?.category));
3132
+ const accentCandidates = available.filter((s)=>!primary.includes(s) && !supporting.includes(s) && $24064302523652b1$export$4343b39fe47bd82c[s].tier <= 2 && !usedCategories.has($24064302523652b1$export$4343b39fe47bd82c[s].category));
3133
+ // Shuffle and take a few
3134
+ const accents = [];
3135
+ const shuffled = [
3136
+ ...accentCandidates
3137
+ ];
3138
+ for(let i = shuffled.length - 1; i > 0; i--){
3139
+ const j = Math.floor(rng() * (i + 1));
3140
+ [shuffled[i], shuffled[j]] = [
3141
+ shuffled[j],
3142
+ shuffled[i]
3143
+ ];
3144
+ }
3145
+ accents.push(...shuffled.slice(0, 3));
3146
+ // For certain archetypes, bias the palette
3147
+ if (archetypeName === "geometric-precision") // Remove blobs and organic shapes from primary
3148
+ return {
3149
+ primary: primary.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]?.category !== "procedural" || s === "ngon"),
3150
+ supporting: supporting.filter((s)=>s !== "blob"),
3151
+ accents: accents
3152
+ };
3153
+ if (archetypeName === "organic-flow") {
3154
+ // Boost procedural/organic shapes
3155
+ const organicBoost = available.filter((s)=>[
3156
+ "blob",
3157
+ "superellipse",
3158
+ "waveRing",
3159
+ "rose"
3160
+ ].includes(s) && !primary.includes(s));
3161
+ return {
3162
+ primary: [
3163
+ ...primary,
3164
+ ...organicBoost.slice(0, 2)
3165
+ ],
3166
+ supporting: supporting,
3167
+ accents: accents
3168
+ };
3169
+ }
3170
+ if (archetypeName === "shattered-glass") {
3171
+ // Favor angular, fragmented shapes
3172
+ const shardBoost = available.filter((s)=>[
3173
+ "shardField",
3174
+ "voronoiCell",
3175
+ "penroseTile",
3176
+ "diamond",
3177
+ "triangle",
3178
+ "ngon"
3179
+ ].includes(s) && !primary.includes(s));
3180
+ return {
3181
+ primary: [
3182
+ ...primary.filter((s)=>s !== "blob" && s !== "cloudForm"),
3183
+ ...shardBoost.slice(0, 3)
3184
+ ],
3185
+ supporting: supporting.filter((s)=>s !== "blob" && s !== "cloudForm"),
3186
+ accents: accents
3187
+ };
3188
+ }
3189
+ if (archetypeName === "botanical") {
3190
+ // Favor organic, flowing shapes
3191
+ const botanicalBoost = available.filter((s)=>[
3192
+ "tendril",
3193
+ "cloudForm",
3194
+ "blob",
3195
+ "crescent",
3196
+ "rose",
3197
+ "inkSplat"
3198
+ ].includes(s) && !primary.includes(s));
3199
+ return {
3200
+ primary: [
3201
+ ...primary,
3202
+ ...botanicalBoost.slice(0, 3)
3203
+ ],
3204
+ supporting: supporting,
3205
+ accents: accents
3206
+ };
3207
+ }
3208
+ if (archetypeName === "stipple-portrait") {
3209
+ // Favor small, dot-friendly shapes
3210
+ const stippleBoost = available.filter((s)=>[
3211
+ "dotCluster",
3212
+ "circle",
3213
+ "crosshatchPatch",
3214
+ "voronoiCell",
3215
+ "blob"
3216
+ ].includes(s) && !primary.includes(s));
3217
+ return {
3218
+ primary: [
3219
+ ...primary,
3220
+ ...stippleBoost.slice(0, 3)
3221
+ ],
3222
+ supporting: supporting,
3223
+ accents: accents
3224
+ };
3225
+ }
3226
+ if (archetypeName === "celestial") {
3227
+ // Favor sacred geometry and cosmic shapes
3228
+ const celestialBoost = available.filter((s)=>[
3229
+ "crescent",
3230
+ "geodesicDome",
3231
+ "mandala",
3232
+ "flowerOfLife",
3233
+ "spirograph",
3234
+ "fibonacciSpiral"
3235
+ ].includes(s) && !primary.includes(s));
3236
+ return {
3237
+ primary: [
3238
+ ...primary,
3239
+ ...celestialBoost.slice(0, 3)
3240
+ ],
3241
+ supporting: supporting,
3242
+ accents: accents
3243
+ };
3244
+ }
3245
+ return {
3246
+ primary: primary,
3247
+ supporting: supporting,
3248
+ accents: accents
2358
3249
  };
2359
3250
  }
2360
3251
  function $24064302523652b1$export$3c37d9a045754d0e(palette, rng, sizeFraction) {
@@ -2688,10 +3579,138 @@ const $3faa2521b78398cf$var$ARCHETYPES = [
2688
3579
  glowMultiplier: 1,
2689
3580
  sizePower: 1.8,
2690
3581
  invertForeground: false
3582
+ },
3583
+ {
3584
+ name: "shattered-glass",
3585
+ gridSize: 8,
3586
+ layers: 3,
3587
+ baseOpacity: 0.85,
3588
+ opacityReduction: 0.1,
3589
+ minShapeSize: 15,
3590
+ maxShapeSize: 250,
3591
+ backgroundStyle: "solid-dark",
3592
+ paletteMode: "high-contrast",
3593
+ preferredStyles: [
3594
+ "fill-and-stroke",
3595
+ "stroke-only",
3596
+ "fill-only"
3597
+ ],
3598
+ flowLineMultiplier: 0,
3599
+ heroShape: false,
3600
+ glowMultiplier: 0.3,
3601
+ sizePower: 1.0,
3602
+ invertForeground: false
3603
+ },
3604
+ {
3605
+ name: "botanical",
3606
+ gridSize: 4,
3607
+ layers: 4,
3608
+ baseOpacity: 0.5,
3609
+ opacityReduction: 0.06,
3610
+ minShapeSize: 30,
3611
+ maxShapeSize: 400,
3612
+ backgroundStyle: "radial-light",
3613
+ paletteMode: "earth",
3614
+ preferredStyles: [
3615
+ "watercolor",
3616
+ "fill-only",
3617
+ "incomplete"
3618
+ ],
3619
+ flowLineMultiplier: 3,
3620
+ heroShape: true,
3621
+ glowMultiplier: 0.2,
3622
+ sizePower: 1.6,
3623
+ invertForeground: false
3624
+ },
3625
+ {
3626
+ name: "stipple-portrait",
3627
+ gridSize: 9,
3628
+ layers: 2,
3629
+ baseOpacity: 0.8,
3630
+ opacityReduction: 0.05,
3631
+ minShapeSize: 5,
3632
+ maxShapeSize: 120,
3633
+ backgroundStyle: "solid-light",
3634
+ paletteMode: "monochrome",
3635
+ preferredStyles: [
3636
+ "stipple",
3637
+ "fill-only",
3638
+ "hatched"
3639
+ ],
3640
+ flowLineMultiplier: 0,
3641
+ heroShape: false,
3642
+ glowMultiplier: 0,
3643
+ sizePower: 2.8,
3644
+ invertForeground: false
3645
+ },
3646
+ {
3647
+ name: "celestial",
3648
+ gridSize: 7,
3649
+ layers: 5,
3650
+ baseOpacity: 0.45,
3651
+ opacityReduction: 0.04,
3652
+ minShapeSize: 8,
3653
+ maxShapeSize: 450,
3654
+ backgroundStyle: "radial-dark",
3655
+ paletteMode: "neon",
3656
+ preferredStyles: [
3657
+ "fill-only",
3658
+ "watercolor",
3659
+ "stroke-only",
3660
+ "incomplete"
3661
+ ],
3662
+ flowLineMultiplier: 2,
3663
+ heroShape: true,
3664
+ glowMultiplier: 2.5,
3665
+ sizePower: 2.2,
3666
+ invertForeground: false
2691
3667
  }
2692
3668
  ];
3669
+ /**
3670
+ * Linearly interpolate between two archetype numeric parameters.
3671
+ */ function $3faa2521b78398cf$var$lerpNum(a, b, t) {
3672
+ return a + (b - a) * t;
3673
+ }
3674
+ /**
3675
+ * Blend two archetypes by interpolating their numeric parameters
3676
+ * and merging their style arrays.
3677
+ */ function $3faa2521b78398cf$var$blendArchetypes(a, b, t) {
3678
+ // Merge preferred styles — unique union, primary archetype first
3679
+ const mergedStyles = [
3680
+ ...new Set([
3681
+ ...a.preferredStyles,
3682
+ ...b.preferredStyles
3683
+ ])
3684
+ ];
3685
+ return {
3686
+ name: `${a.name}+${b.name}`,
3687
+ gridSize: Math.round($3faa2521b78398cf$var$lerpNum(a.gridSize, b.gridSize, t)),
3688
+ layers: Math.round($3faa2521b78398cf$var$lerpNum(a.layers, b.layers, t)),
3689
+ baseOpacity: $3faa2521b78398cf$var$lerpNum(a.baseOpacity, b.baseOpacity, t),
3690
+ opacityReduction: $3faa2521b78398cf$var$lerpNum(a.opacityReduction, b.opacityReduction, t),
3691
+ minShapeSize: Math.round($3faa2521b78398cf$var$lerpNum(a.minShapeSize, b.minShapeSize, t)),
3692
+ maxShapeSize: Math.round($3faa2521b78398cf$var$lerpNum(a.maxShapeSize, b.maxShapeSize, t)),
3693
+ backgroundStyle: t < 0.5 ? a.backgroundStyle : b.backgroundStyle,
3694
+ paletteMode: t < 0.5 ? a.paletteMode : b.paletteMode,
3695
+ preferredStyles: mergedStyles,
3696
+ flowLineMultiplier: $3faa2521b78398cf$var$lerpNum(a.flowLineMultiplier, b.flowLineMultiplier, t),
3697
+ heroShape: t < 0.5 ? a.heroShape : b.heroShape,
3698
+ glowMultiplier: $3faa2521b78398cf$var$lerpNum(a.glowMultiplier, b.glowMultiplier, t),
3699
+ sizePower: $3faa2521b78398cf$var$lerpNum(a.sizePower, b.sizePower, t),
3700
+ invertForeground: t < 0.5 ? a.invertForeground : b.invertForeground
3701
+ };
3702
+ }
2693
3703
  function $3faa2521b78398cf$export$f1142fd7da4d6590(rng) {
2694
- return $3faa2521b78398cf$var$ARCHETYPES[Math.floor(rng() * $3faa2521b78398cf$var$ARCHETYPES.length)];
3704
+ const primary = $3faa2521b78398cf$var$ARCHETYPES[Math.floor(rng() * $3faa2521b78398cf$var$ARCHETYPES.length)];
3705
+ // ~15% chance of blending with a second archetype
3706
+ if (rng() < 0.15) {
3707
+ const secondary = $3faa2521b78398cf$var$ARCHETYPES[Math.floor(rng() * $3faa2521b78398cf$var$ARCHETYPES.length)];
3708
+ if (secondary.name !== primary.name) {
3709
+ const blendT = 0.25 + rng() * 0.25; // 25-50% blend toward secondary
3710
+ return $3faa2521b78398cf$var$blendArchetypes(primary, secondary, blendT);
3711
+ }
3712
+ }
3713
+ return primary;
2695
3714
  }
2696
3715
 
2697
3716
 
@@ -2867,6 +3886,135 @@ function $b623126c6e9cbb71$var$drawBackground(ctx, style, bgStart, bgEnd, width,
2867
3886
  }
2868
3887
  }
2869
3888
  }
3889
+ const $b623126c6e9cbb71$var$CONSTELLATIONS = [
3890
+ {
3891
+ name: "flanked-triangle",
3892
+ build: (rng, baseSize)=>{
3893
+ const gap = baseSize * (0.6 + rng() * 0.3);
3894
+ return [
3895
+ {
3896
+ dx: 0,
3897
+ dy: 0,
3898
+ shape: "triangle",
3899
+ size: baseSize,
3900
+ rotation: rng() * 360
3901
+ },
3902
+ {
3903
+ dx: -gap,
3904
+ dy: gap * 0.3,
3905
+ shape: "circle",
3906
+ size: baseSize * 0.35,
3907
+ rotation: 0
3908
+ },
3909
+ {
3910
+ dx: gap,
3911
+ dy: gap * 0.3,
3912
+ shape: "circle",
3913
+ size: baseSize * 0.35,
3914
+ rotation: 0
3915
+ }
3916
+ ];
3917
+ }
3918
+ },
3919
+ {
3920
+ name: "hexagon-ring",
3921
+ build: (rng, baseSize)=>{
3922
+ const members = [];
3923
+ const count = 5 + Math.floor(rng() * 2);
3924
+ const ringR = baseSize * 0.6;
3925
+ for(let i = 0; i < count; i++){
3926
+ const angle = i / count * Math.PI * 2;
3927
+ members.push({
3928
+ dx: Math.cos(angle) * ringR,
3929
+ dy: Math.sin(angle) * ringR,
3930
+ shape: "hexagon",
3931
+ size: baseSize * (0.25 + rng() * 0.1),
3932
+ rotation: angle * 180 / Math.PI
3933
+ });
3934
+ }
3935
+ return members;
3936
+ }
3937
+ },
3938
+ {
3939
+ name: "spiral-dots",
3940
+ build: (rng, baseSize)=>{
3941
+ const members = [];
3942
+ const count = 7 + Math.floor(rng() * 5);
3943
+ const turns = 1.5 + rng();
3944
+ for(let i = 0; i < count; i++){
3945
+ const t = i / count;
3946
+ const angle = t * Math.PI * 2 * turns;
3947
+ const r = t * baseSize * 0.7;
3948
+ members.push({
3949
+ dx: Math.cos(angle) * r,
3950
+ dy: Math.sin(angle) * r,
3951
+ shape: "circle",
3952
+ size: baseSize * (0.08 + (1 - t) * 0.12),
3953
+ rotation: 0
3954
+ });
3955
+ }
3956
+ return members;
3957
+ }
3958
+ },
3959
+ {
3960
+ name: "diamond-cluster",
3961
+ build: (rng, baseSize)=>{
3962
+ const gap = baseSize * 0.45;
3963
+ return [
3964
+ {
3965
+ dx: 0,
3966
+ dy: -gap,
3967
+ shape: "diamond",
3968
+ size: baseSize * 0.4,
3969
+ rotation: 0
3970
+ },
3971
+ {
3972
+ dx: gap,
3973
+ dy: 0,
3974
+ shape: "diamond",
3975
+ size: baseSize * 0.35,
3976
+ rotation: 15
3977
+ },
3978
+ {
3979
+ dx: 0,
3980
+ dy: gap,
3981
+ shape: "diamond",
3982
+ size: baseSize * 0.3,
3983
+ rotation: 30
3984
+ },
3985
+ {
3986
+ dx: -gap,
3987
+ dy: 0,
3988
+ shape: "diamond",
3989
+ size: baseSize * 0.35,
3990
+ rotation: -15
3991
+ }
3992
+ ];
3993
+ }
3994
+ },
3995
+ {
3996
+ name: "crescent-pair",
3997
+ build: (rng, baseSize)=>{
3998
+ const gap = baseSize * 0.5;
3999
+ return [
4000
+ {
4001
+ dx: -gap * 0.4,
4002
+ dy: 0,
4003
+ shape: "crescent",
4004
+ size: baseSize * 0.5,
4005
+ rotation: rng() * 30
4006
+ },
4007
+ {
4008
+ dx: gap * 0.4,
4009
+ dy: 0,
4010
+ shape: "crescent",
4011
+ size: baseSize * 0.45,
4012
+ rotation: 180 + rng() * 30
4013
+ }
4014
+ ];
4015
+ }
4016
+ }
4017
+ ];
2870
4018
  function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
2871
4019
  const finalConfig = {
2872
4020
  ...(0, $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f),
@@ -2898,6 +4046,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
2898
4046
  const colorGrade = (0, $9d614e7d77fc2947$export$6d1620b367f86f7a)(rng);
2899
4047
  // ── 0e. Light direction — consistent shadow angle ──────────────
2900
4048
  const lightAngle = rng() * Math.PI * 2;
4049
+ // ── 0f. Palette evolution — hue drift direction across layers ──
4050
+ const paletteHueShift = (rng() - 0.5) * 40; // -20° to +20° total drift
2901
4051
  const scaleFactor = Math.min(width, height) / 1024;
2902
4052
  const adjustedMinSize = minShapeSize * scaleFactor;
2903
4053
  const adjustedMaxSize = maxShapeSize * scaleFactor;
@@ -2953,6 +4103,65 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
2953
4103
  ctx.stroke();
2954
4104
  }
2955
4105
  ctx.globalCompositeOperation = "source-over";
4106
+ // ── 1c. Background pattern layer — subtle textured paper ───────
4107
+ const bgPatternRoll = rng();
4108
+ if (bgPatternRoll < 0.6) {
4109
+ ctx.save();
4110
+ ctx.globalCompositeOperation = "soft-light";
4111
+ const patternOpacity = 0.02 + rng() * 0.04;
4112
+ const patternColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
4113
+ if (bgPatternRoll < 0.2) {
4114
+ // Dot grid
4115
+ const dotSpacing = Math.max(8, Math.min(width, height) * (0.015 + rng() * 0.015));
4116
+ const dotR = dotSpacing * 0.08;
4117
+ ctx.globalAlpha = patternOpacity;
4118
+ ctx.fillStyle = patternColor;
4119
+ for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
4120
+ ctx.beginPath();
4121
+ ctx.arc(px, py, dotR, 0, Math.PI * 2);
4122
+ ctx.fill();
4123
+ }
4124
+ } else if (bgPatternRoll < 0.4) {
4125
+ // Diagonal lines
4126
+ const lineSpacing = Math.max(6, Math.min(width, height) * (0.02 + rng() * 0.02));
4127
+ ctx.globalAlpha = patternOpacity;
4128
+ ctx.strokeStyle = patternColor;
4129
+ ctx.lineWidth = 0.5 * scaleFactor;
4130
+ const diag = Math.hypot(width, height);
4131
+ for(let d = -diag; d < diag; d += lineSpacing){
4132
+ ctx.beginPath();
4133
+ ctx.moveTo(d, 0);
4134
+ ctx.lineTo(d + height, height);
4135
+ ctx.stroke();
4136
+ }
4137
+ } else {
4138
+ // Tessellation — hexagonal grid of tiny shapes
4139
+ const tessSize = Math.max(10, Math.min(width, height) * (0.025 + rng() * 0.02));
4140
+ const tessH = tessSize * Math.sqrt(3);
4141
+ ctx.globalAlpha = patternOpacity * 0.7;
4142
+ ctx.strokeStyle = patternColor;
4143
+ ctx.lineWidth = 0.4 * scaleFactor;
4144
+ for(let row = 0; row * tessH < height + tessH; row++){
4145
+ const offsetX = row % 2 * tessSize * 0.75;
4146
+ for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
4147
+ const hx = col * tessSize * 1.5 + offsetX;
4148
+ const hy = row * tessH;
4149
+ ctx.beginPath();
4150
+ for(let s = 0; s < 6; s++){
4151
+ const angle = Math.PI / 3 * s - Math.PI / 6;
4152
+ const vx = hx + Math.cos(angle) * tessSize * 0.5;
4153
+ const vy = hy + Math.sin(angle) * tessSize * 0.5;
4154
+ if (s === 0) ctx.moveTo(vx, vy);
4155
+ else ctx.lineTo(vx, vy);
4156
+ }
4157
+ ctx.closePath();
4158
+ ctx.stroke();
4159
+ }
4160
+ }
4161
+ }
4162
+ ctx.restore();
4163
+ }
4164
+ ctx.globalCompositeOperation = "source-over";
2956
4165
  // ── 2. Composition mode ────────────────────────────────────────
2957
4166
  const compositionMode = $b623126c6e9cbb71$var$COMPOSITION_MODES[Math.floor(rng() * $b623126c6e9cbb71$var$COMPOSITION_MODES.length)];
2958
4167
  const symRoll = rng();
@@ -3013,6 +4222,41 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3013
4222
  ry + (nearest.y - ry) * pull
3014
4223
  ];
3015
4224
  }
4225
+ // ── 3b. Void zone decoration — intentional negative space ────
4226
+ for (const zone of voidZones){
4227
+ // Subtle halo ring around void zones
4228
+ ctx.globalAlpha = 0.04 + rng() * 0.04;
4229
+ ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
4230
+ ctx.lineWidth = 1.5 * scaleFactor;
4231
+ ctx.beginPath();
4232
+ ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2);
4233
+ ctx.stroke();
4234
+ // ~50% chance: scatter tiny dots inside the void
4235
+ if (rng() < 0.5) {
4236
+ const dotCount = 3 + Math.floor(rng() * 6);
4237
+ ctx.globalAlpha = 0.06 + rng() * 0.04;
4238
+ ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
4239
+ for(let d = 0; d < dotCount; d++){
4240
+ const angle = rng() * Math.PI * 2;
4241
+ const dist = rng() * zone.radius * 0.7;
4242
+ const dotR = (1 + rng() * 3) * scaleFactor;
4243
+ ctx.beginPath();
4244
+ ctx.arc(zone.x + Math.cos(angle) * dist, zone.y + Math.sin(angle) * dist, dotR, 0, Math.PI * 2);
4245
+ ctx.fill();
4246
+ }
4247
+ }
4248
+ // ~30% chance: thin concentric ring inside
4249
+ if (rng() < 0.3) {
4250
+ ctx.globalAlpha = 0.03 + rng() * 0.03;
4251
+ ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
4252
+ ctx.lineWidth = 0.5 * scaleFactor;
4253
+ const innerR = zone.radius * (0.4 + rng() * 0.3);
4254
+ ctx.beginPath();
4255
+ ctx.arc(zone.x, zone.y, innerR, 0, Math.PI * 2);
4256
+ ctx.stroke();
4257
+ }
4258
+ }
4259
+ ctx.globalAlpha = 1;
3016
4260
  // ── 4. Flow field seed values ──────────────────────────────────
3017
4261
  const fieldAngleBase = rng() * Math.PI * 2;
3018
4262
  const fieldFreq = 0.5 + rng() * 2;
@@ -3080,6 +4324,23 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3080
4324
  const layerRenderStyle = rng() < 0.6 ? archetype.preferredStyles[Math.floor(rng() * archetype.preferredStyles.length)] : (0, $9beb8f41637c29fd$export$9fd4e64b2acd410e)(rng);
3081
4325
  // Atmospheric desaturation for later layers
3082
4326
  const atmosphericDesat = layerRatio * 0.3;
4327
+ // Depth-of-field simulation — later layers are "further away"
4328
+ // Reduce stroke widths and shift colors toward the background
4329
+ const dofFactor = 1 - layerRatio * 0.5; // 1.0 for front layer, 0.5 for back
4330
+ const dofStrokeScale = 0.4 + dofFactor * 0.6; // strokes thin out with depth
4331
+ const dofContrastReduction = layerRatio * 0.2; // colors fade toward bg
4332
+ // Color palette evolution — hue-rotate the hierarchy per layer
4333
+ const layerHierarchy = (0, $9d614e7d77fc2947$export$703ba40a4347f77a)(colorHierarchy, layerRatio, paletteHueShift);
4334
+ // Focal depth: shapes near focal points get more detail
4335
+ const focalDetailBoost = (px, py)=>{
4336
+ let minFocalDist = Infinity;
4337
+ for (const fp of focalPoints){
4338
+ const d = Math.hypot(px - fp.x, py - fp.y);
4339
+ if (d < minFocalDist) minFocalDist = d;
4340
+ }
4341
+ const maxDist = Math.hypot(width, height) * 0.5;
4342
+ return Math.max(0, 1 - minFocalDist / maxDist); // 1.0 at focal, 0.0 at edges
4343
+ };
3083
4344
  for(let i = 0; i < numShapes; i++){
3084
4345
  // Position from composition mode, then focal bias
3085
4346
  const rawPos = $b623126c6e9cbb71$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
@@ -3110,9 +4371,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3110
4371
  rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
3111
4372
  }
3112
4373
  }
3113
- // Positional color from hierarchy + jitter
3114
- let fillBase = $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, colorHierarchy, rng);
3115
- const strokeBase = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng);
4374
+ // Positional color from hierarchy + jitter (using evolved layer palette)
4375
+ let fillBase = $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, layerHierarchy, rng);
4376
+ const strokeBase = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(layerHierarchy, rng);
3116
4377
  // Desaturate colors on later layers for depth
3117
4378
  if (atmosphericDesat > 0) fillBase = (0, $9d614e7d77fc2947$export$fb75607d98509d9)(fillBase, atmosphericDesat);
3118
4379
  // Temperature contrast: shift foreground shapes opposite to background
@@ -3122,8 +4383,10 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3122
4383
  // Semi-transparent fill
3123
4384
  const fillAlpha = 0.2 + rng() * 0.5;
3124
4385
  const transparentFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, fillAlpha);
3125
- const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor;
3126
- ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5);
4386
+ const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor * dofStrokeScale;
4387
+ // Depth-of-field: reduce opacity slightly for distant layers
4388
+ const dofOpacityScale = 1 - dofContrastReduction;
4389
+ ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5) * dofOpacityScale;
3127
4390
  // Glow on sacred shapes more often — scaled by archetype
3128
4391
  const isSacred = $b623126c6e9cbb71$var$SACRED_SHAPES.includes(shape);
3129
4392
  const baseGlowChance = isSacred ? 0.45 : 0.2;
@@ -3142,7 +4405,48 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3142
4405
  const shadowDist = hasGlow ? 0 : size * 0.02;
3143
4406
  const shadowOffX = shadowDist * Math.cos(lightAngle);
3144
4407
  const shadowOffY = shadowDist * Math.sin(lightAngle);
3145
- (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
4408
+ // ── 5a. Tangent placement — nudge toward nearest shape edge ──
4409
+ let finalX = x;
4410
+ let finalY = y;
4411
+ if (shapePositions.length > 0 && rng() < 0.25) {
4412
+ // Find nearest placed shape
4413
+ let nearestDist = Infinity;
4414
+ let nearestPos = null;
4415
+ for (const sp of shapePositions){
4416
+ const d = Math.hypot(x - sp.x, y - sp.y);
4417
+ if (d < nearestDist && d > 0) {
4418
+ nearestDist = d;
4419
+ nearestPos = sp;
4420
+ }
4421
+ }
4422
+ if (nearestPos) {
4423
+ // Target distance: edges kissing (sum of half-sizes)
4424
+ const targetDist = (size + nearestPos.size) * 0.5;
4425
+ if (nearestDist > targetDist * 0.5 && nearestDist < targetDist * 3) {
4426
+ const angle = Math.atan2(y - nearestPos.y, x - nearestPos.x);
4427
+ finalX = nearestPos.x + Math.cos(angle) * targetDist;
4428
+ finalY = nearestPos.y + Math.sin(angle) * targetDist;
4429
+ // Keep in bounds
4430
+ finalX = Math.max(0, Math.min(width, finalX));
4431
+ finalY = Math.max(0, Math.min(height, finalY));
4432
+ }
4433
+ }
4434
+ }
4435
+ // ── 5b. Shape mirroring — basic shapes get reflected copies ──
4436
+ const mirrorAxis = (0, $9beb8f41637c29fd$export$879206e23912d1a9)(rng);
4437
+ const isBasicShape = [
4438
+ "circle",
4439
+ "triangle",
4440
+ "square",
4441
+ "hexagon",
4442
+ "star",
4443
+ "diamond",
4444
+ "crescent",
4445
+ "penroseTile",
4446
+ "reuleauxTriangle"
4447
+ ].includes(shape);
4448
+ const shouldMirror = mirrorAxis !== null && isBasicShape && size > adjustedMaxSize * 0.2;
4449
+ const shapeConfig = {
3146
4450
  fillColor: transparentFill,
3147
4451
  strokeColor: strokeColor,
3148
4452
  strokeWidth: strokeWidth,
@@ -3154,22 +4458,47 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3154
4458
  gradientFillEnd: gradientEnd,
3155
4459
  renderStyle: finalRenderStyle,
3156
4460
  rng: rng
4461
+ };
4462
+ if (shouldMirror) (0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
4463
+ ...shapeConfig,
4464
+ mirrorAxis: mirrorAxis,
4465
+ mirrorGap: size * (0.1 + rng() * 0.3)
3157
4466
  });
4467
+ else (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
4468
+ // ── Glazing — luminous multi-pass transparency on ~20% of shapes ──
4469
+ if (rng() < 0.2 && size > adjustedMinSize * 2) {
4470
+ const glazePasses = 2 + Math.floor(rng() * 2);
4471
+ for(let g = 0; g < glazePasses; g++){
4472
+ const glazeScale = 1 - (g + 1) * 0.12; // progressively smaller
4473
+ const glazeAlpha = 0.08 + g * 0.04; // progressively more opaque toward center
4474
+ ctx.globalAlpha = glazeAlpha;
4475
+ (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, {
4476
+ fillColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.15 + g * 0.1),
4477
+ strokeColor: "rgba(0,0,0,0)",
4478
+ strokeWidth: 0,
4479
+ size: size * glazeScale,
4480
+ rotation: rotation,
4481
+ proportionType: "GOLDEN_RATIO",
4482
+ renderStyle: "fill-only",
4483
+ rng: rng
4484
+ });
4485
+ }
4486
+ }
3158
4487
  shapePositions.push({
3159
- x: x,
3160
- y: y,
4488
+ x: finalX,
4489
+ y: finalY,
3161
4490
  size: size,
3162
4491
  shape: shape
3163
4492
  });
3164
- // ── 5b. Size echo — large shapes spawn trailing smaller copies ──
4493
+ // ── 5c. Size echo — large shapes spawn trailing smaller copies ──
3165
4494
  if (size > adjustedMaxSize * 0.5 && rng() < 0.2) {
3166
4495
  const echoCount = 2 + Math.floor(rng() * 2);
3167
4496
  const echoAngle = rng() * Math.PI * 2;
3168
4497
  for(let e = 0; e < echoCount; e++){
3169
4498
  const echoScale = 0.3 - e * 0.08;
3170
4499
  const echoDist = size * (0.6 + e * 0.4);
3171
- const echoX = x + Math.cos(echoAngle) * echoDist;
3172
- const echoY = y + Math.sin(echoAngle) * echoDist;
4500
+ const echoX = finalX + Math.cos(echoAngle) * echoDist;
4501
+ const echoY = finalY + Math.sin(echoAngle) * echoDist;
3173
4502
  const echoSize = size * Math.max(0.1, echoScale);
3174
4503
  if (echoX < 0 || echoX > width || echoY < 0 || echoY > height) continue;
3175
4504
  ctx.globalAlpha = layerOpacity * (0.4 - e * 0.1);
@@ -3191,8 +4520,11 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3191
4520
  });
3192
4521
  }
3193
4522
  }
3194
- // ── 5c. Recursive nesting ──────────────────────────────────
3195
- if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
4523
+ // ── 5d. Recursive nesting ──────────────────────────────────
4524
+ // Focal depth: shapes near focal points get more detail
4525
+ const focalProximity = focalDetailBoost(finalX, finalY);
4526
+ const nestingChance = 0.15 + focalProximity * 0.15; // 15-30% near focal
4527
+ if (size > adjustedMaxSize * 0.4 && rng() < nestingChance) {
3196
4528
  const innerCount = 1 + Math.floor(rng() * 3);
3197
4529
  for(let n = 0; n < innerCount; n++){
3198
4530
  // Pick inner shape from palette affinities
@@ -3204,7 +4536,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3204
4536
  const innerRot = rng() * 360;
3205
4537
  const innerFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1), 0.3 + rng() * 0.4);
3206
4538
  ctx.globalAlpha = layerOpacity * 0.7;
3207
- (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, innerShape, x + innerOffX, y + innerOffY, {
4539
+ (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, innerShape, finalX + innerOffX, finalY + innerOffY, {
3208
4540
  fillColor: innerFill,
3209
4541
  strokeColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(strokeColor, 0.5),
3210
4542
  strokeWidth: strokeWidth * 0.6,
@@ -3216,6 +4548,42 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3216
4548
  });
3217
4549
  }
3218
4550
  }
4551
+ // ── 5e. Shape constellations — pre-composed groups ─────────
4552
+ const constellationChance = 0.12 + focalProximity * 0.1; // 12-22% near focal
4553
+ if (size > adjustedMaxSize * 0.35 && rng() < constellationChance) {
4554
+ const constellation = $b623126c6e9cbb71$var$CONSTELLATIONS[Math.floor(rng() * $b623126c6e9cbb71$var$CONSTELLATIONS.length)];
4555
+ const members = constellation.build(rng, size);
4556
+ const groupRotation = rng() * Math.PI * 2;
4557
+ const cosR = Math.cos(groupRotation);
4558
+ const sinR = Math.sin(groupRotation);
4559
+ for (const member of members){
4560
+ // Rotate the group offset by the group rotation
4561
+ const mx = finalX + member.dx * cosR - member.dy * sinR;
4562
+ const my = finalY + member.dx * sinR + member.dy * cosR;
4563
+ if (mx < 0 || mx > width || my < 0 || my > height) continue;
4564
+ const memberFill = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 8, 0.06), fillAlpha * 0.8);
4565
+ const memberStroke = (0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
4566
+ ctx.globalAlpha = layerOpacity * 0.6;
4567
+ // Use the member's shape if available, otherwise fall back to palette
4568
+ const memberShape = shapeNames.includes(member.shape) ? member.shape : (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, member.size / adjustedMaxSize);
4569
+ (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, memberShape, mx, my, {
4570
+ fillColor: memberFill,
4571
+ strokeColor: memberStroke,
4572
+ strokeWidth: strokeWidth * 0.7,
4573
+ size: member.size,
4574
+ rotation: member.rotation + groupRotation * 180 / Math.PI,
4575
+ proportionType: "GOLDEN_RATIO",
4576
+ renderStyle: (0, $24064302523652b1$export$ab873bb6fb56c1a8)(memberShape, layerRenderStyle, rng),
4577
+ rng: rng
4578
+ });
4579
+ shapePositions.push({
4580
+ x: mx,
4581
+ y: my,
4582
+ size: member.size,
4583
+ shape: memberShape
4584
+ });
4585
+ }
4586
+ }
3219
4587
  }
3220
4588
  }
3221
4589
  // Reset blend mode for post-processing passes
@@ -3286,7 +4654,41 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3286
4654
  prevY = fy;
3287
4655
  }
3288
4656
  }
3289
- // ── 6b. Apply symmetry mirroring ─────────────────────────────────
4657
+ // ── 6b. Motion/energy lines short directional bursts ─────────
4658
+ const energyArchetypes = [
4659
+ "dense-chaotic",
4660
+ "cosmic",
4661
+ "neon-glow",
4662
+ "bold-graphic"
4663
+ ];
4664
+ const hasEnergyLines = energyArchetypes.some((a)=>archetype.name.includes(a)) || rng() < 0.25;
4665
+ if (hasEnergyLines && shapePositions.length > 0) {
4666
+ const energyCount = 5 + Math.floor(rng() * 10);
4667
+ ctx.lineCap = "round";
4668
+ for(let e = 0; e < energyCount; e++){
4669
+ // Pick a random shape to radiate from
4670
+ const source = shapePositions[Math.floor(rng() * shapePositions.length)];
4671
+ const burstCount = 2 + Math.floor(rng() * 4);
4672
+ const baseAngle = flowAngle(source.x, source.y);
4673
+ for(let b = 0; b < burstCount; b++){
4674
+ const angle = baseAngle + (rng() - 0.5) * 1.2;
4675
+ const lineLen = (source.size * 0.3 + rng() * source.size * 0.5) * scaleFactor * 0.3;
4676
+ const startDist = source.size * 0.5;
4677
+ const sx = source.x + Math.cos(angle) * startDist;
4678
+ const sy = source.y + Math.sin(angle) * startDist;
4679
+ const ex = sx + Math.cos(angle) * lineLen;
4680
+ const ey = sy + Math.sin(angle) * lineLen;
4681
+ ctx.globalAlpha = 0.04 + rng() * 0.06;
4682
+ ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
4683
+ ctx.lineWidth = (0.5 + rng() * 1.5) * scaleFactor;
4684
+ ctx.beginPath();
4685
+ ctx.moveTo(sx, sy);
4686
+ ctx.lineTo(ex, ey);
4687
+ ctx.stroke();
4688
+ }
4689
+ }
4690
+ }
4691
+ // ── 6c. Apply symmetry mirroring ─────────────────────────────────
3290
4692
  if (symmetryMode !== "none") {
3291
4693
  const canvas = ctx.canvas;
3292
4694
  ctx.save();
@@ -3397,6 +4799,44 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
3397
4799
  ctx.restore();
3398
4800
  ctx.globalCompositeOperation = "source-over";
3399
4801
  }
4802
+ // ── 11. Signature mark — unique geometric chop from hash prefix ──
4803
+ {
4804
+ const sigRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 42));
4805
+ const sigSize = Math.min(width, height) * 0.025;
4806
+ // Bottom-right corner with padding
4807
+ const sigX = width - sigSize * 2.5;
4808
+ const sigY = height - sigSize * 2.5;
4809
+ const sigSegments = 4 + Math.floor(sigRng() * 4); // 4-7 segments
4810
+ const sigColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.15);
4811
+ ctx.save();
4812
+ ctx.globalAlpha = 0.12 + sigRng() * 0.08;
4813
+ ctx.translate(sigX, sigY);
4814
+ ctx.strokeStyle = sigColor;
4815
+ ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.06);
4816
+ ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
4817
+ // Outer ring
4818
+ ctx.beginPath();
4819
+ ctx.arc(0, 0, sigSize, 0, Math.PI * 2);
4820
+ ctx.stroke();
4821
+ ctx.fill();
4822
+ // Inner geometric pattern — unique per hash
4823
+ ctx.beginPath();
4824
+ for(let s = 0; s < sigSegments; s++){
4825
+ const angle1 = sigRng() * Math.PI * 2;
4826
+ const angle2 = sigRng() * Math.PI * 2;
4827
+ const r1 = sigSize * (0.2 + sigRng() * 0.6);
4828
+ const r2 = sigSize * (0.2 + sigRng() * 0.6);
4829
+ ctx.moveTo(Math.cos(angle1) * r1, Math.sin(angle1) * r1);
4830
+ ctx.lineTo(Math.cos(angle2) * r2, Math.sin(angle2) * r2);
4831
+ }
4832
+ ctx.stroke();
4833
+ // Center dot
4834
+ ctx.beginPath();
4835
+ ctx.arc(0, 0, sigSize * 0.12, 0, Math.PI * 2);
4836
+ ctx.fillStyle = sigColor;
4837
+ ctx.fill();
4838
+ ctx.restore();
4839
+ }
3400
4840
  ctx.globalAlpha = 1;
3401
4841
  }
3402
4842