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