git-hash-art 0.6.0 → 0.8.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/main.js CHANGED
@@ -27,17 +27,22 @@ $parcel$export(module.exports, "DEFAULT_CONFIG", () => $93cf69256c93baa9$export$
27
27
  * identically in Node (@napi-rs/canvas) and browsers.
28
28
  *
29
29
  * Generation pipeline:
30
- * 1. Background radial gradient from hash-derived dark palette
31
- * 1b. Layered background large faint shapes / subtle pattern for depth
32
- * 2. Composition modehash selects: radial, flow-field, spiral, grid-subdivision, or clustered
33
- * 3. Focal points + void zones (negative space)
34
- * 4. Flow field seed values
35
- * 5. Shape layers — blend modes, render styles, weighted selection,
36
- * focal-point placement, atmospheric depth, organic edges
30
+ * 0. Archetype selection + shape palette + color hierarchy
31
+ * 1. Backgroundstyle from archetype, gradient mesh for depth
32
+ * 1b. Layered background archetype-coherent shapes
33
+ * 2. Composition mode + symmetry
34
+ * 3. Focal points + void zones + hero avoidance field
35
+ * 4. Flow field
36
+ * 4b. Hero shape
37
+ * 5. Shape layers — palette-driven selection, affinity-aware styles,
38
+ * size echo, tangent placement, atmospheric depth
37
39
  * 5b. Recursive nesting
38
- * 6. Flow-line passtapered brush-stroke curves
39
- * 7. Noise texture overlay
40
- * 8. Organic connecting curves
40
+ * 6. Flow linesvariable color, branching, pressure simulation
41
+ * 6b. Symmetry mirroring
42
+ * 7. Noise texture
43
+ * 8. Vignette
44
+ * 9. Organic connecting curves
45
+ * 10. Post-processing — color grading, chromatic aberration, bloom
41
46
  */
42
47
  // declare module 'color-scheme';
43
48
 
@@ -411,6 +416,67 @@ function $d016ad53434219a1$export$f2121afcad3d553f(hex, alpha) {
411
416
  const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex);
412
417
  return `rgba(${r},${g},${b},${alpha.toFixed(3)})`;
413
418
  }
419
+ function $d016ad53434219a1$export$fabac4600b87056(colors, rng) {
420
+ if (colors.length < 3) return {
421
+ dominant: colors[0] || "#888888",
422
+ secondary: colors[1] || colors[0] || "#888888",
423
+ accent: colors[colors.length - 1] || "#888888",
424
+ all: colors
425
+ };
426
+ // Pick dominant as the color closest to the palette's average hue
427
+ const hsls = colors.map((c)=>$d016ad53434219a1$var$hexToHsl(c));
428
+ const avgHue = hsls.reduce((s, h)=>s + h[0], 0) / hsls.length;
429
+ let dominantIdx = 0;
430
+ let minDist = 360;
431
+ for(let i = 0; i < hsls.length; i++){
432
+ const d = Math.min(Math.abs(hsls[i][0] - avgHue), 360 - Math.abs(hsls[i][0] - avgHue));
433
+ if (d < minDist) {
434
+ minDist = d;
435
+ dominantIdx = i;
436
+ }
437
+ }
438
+ // Accent is the color most distant from dominant in hue
439
+ let accentIdx = 0;
440
+ let maxDist = 0;
441
+ for(let i = 0; i < hsls.length; i++){
442
+ if (i === dominantIdx) continue;
443
+ const d = Math.min(Math.abs(hsls[i][0] - hsls[dominantIdx][0]), 360 - Math.abs(hsls[i][0] - hsls[dominantIdx][0]));
444
+ if (d > maxDist) {
445
+ maxDist = d;
446
+ accentIdx = i;
447
+ }
448
+ }
449
+ // Secondary is the remaining color with highest saturation
450
+ let secondaryIdx = 0;
451
+ let maxSat = -1;
452
+ for(let i = 0; i < hsls.length; i++){
453
+ if (i === dominantIdx || i === accentIdx) continue;
454
+ if (hsls[i][1] > maxSat) {
455
+ maxSat = hsls[i][1];
456
+ secondaryIdx = i;
457
+ }
458
+ }
459
+ if (secondaryIdx === dominantIdx) secondaryIdx = accentIdx === 0 ? 1 : 0;
460
+ return {
461
+ dominant: colors[dominantIdx],
462
+ secondary: colors[secondaryIdx],
463
+ accent: colors[accentIdx],
464
+ all: colors
465
+ };
466
+ }
467
+ function $d016ad53434219a1$export$b49f62f0a99da0e8(hierarchy, rng) {
468
+ const roll = rng();
469
+ if (roll < 0.60) return hierarchy.dominant;
470
+ if (roll < 0.85) return hierarchy.secondary;
471
+ return hierarchy.accent;
472
+ }
473
+ function $d016ad53434219a1$export$18a34c25ea7e724b(hex, rng, hueAmount = 8, slAmount = 0.06) {
474
+ const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
475
+ const newH = (h + (rng() - 0.5) * hueAmount * 2 + 360) % 360;
476
+ const newS = Math.max(0, Math.min(1, s + (rng() - 0.5) * slAmount * 2));
477
+ const newL = Math.max(0, Math.min(1, l + (rng() - 0.5) * slAmount * 2));
478
+ return $d016ad53434219a1$var$hslToHex(newH, newS, newL);
479
+ }
414
480
  function $d016ad53434219a1$export$59539d800dbe6858(hex, rng, amount = 0.1) {
415
481
  const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex);
416
482
  const jit = ()=>(rng() - 0.5) * 2 * amount * 255;
@@ -426,6 +492,55 @@ function $d016ad53434219a1$export$51ea55f869b7e0d3(hex, target, amount) {
426
492
  const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
427
493
  return $d016ad53434219a1$var$hslToHex($d016ad53434219a1$var$shiftHueToward(h, target, amount), s, l);
428
494
  }
495
+ function $d016ad53434219a1$export$5c6e3c2b59b7fbbe(hex) {
496
+ const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex).map((c)=>{
497
+ const s = c / 255;
498
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
499
+ });
500
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
501
+ }
502
+ function $d016ad53434219a1$export$90ad0e6170cf6af5(fgHex, bgLuminance, minContrast = 0.15) {
503
+ const fgLum = $d016ad53434219a1$export$5c6e3c2b59b7fbbe(fgHex);
504
+ const diff = Math.abs(fgLum - bgLuminance);
505
+ if (diff >= minContrast) return fgHex;
506
+ const [h, s, l] = $d016ad53434219a1$var$hexToHsl(fgHex);
507
+ if (bgLuminance > 0.5) {
508
+ // Light background — darken and boost saturation
509
+ const targetL = Math.max(0.08, l - (minContrast - diff) * 1.5);
510
+ const targetS = Math.min(1, s + 0.2);
511
+ return $d016ad53434219a1$var$hslToHex(h, targetS, targetL);
512
+ } else {
513
+ // Dark background — lighten and boost saturation
514
+ const targetL = Math.min(0.92, l + (minContrast - diff) * 1.5);
515
+ const targetS = Math.min(1, s + 0.15);
516
+ return $d016ad53434219a1$var$hslToHex(h, targetS, targetL);
517
+ }
518
+ }
519
+ function $d016ad53434219a1$export$4a3734b8c4b5c0e(hex, gradeHue, intensity) {
520
+ const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
521
+ // Blend hue toward the grade hue
522
+ const hueDiff = (gradeHue - h + 540) % 360 - 180;
523
+ const newH = (h + hueDiff * intensity * 0.3 + 360) % 360;
524
+ // Slightly unify saturation
525
+ const newS = Math.max(0, Math.min(1, s + (0.5 - s) * intensity * 0.15));
526
+ return $d016ad53434219a1$var$hslToHex(newH, newS, l);
527
+ }
528
+ function $d016ad53434219a1$export$6d1620b367f86f7a(rng) {
529
+ // Warm golden, cool blue, rosy, teal, amber
530
+ const GRADE_HUES = [
531
+ 40,
532
+ 220,
533
+ 340,
534
+ 175,
535
+ 30
536
+ ];
537
+ const hue = GRADE_HUES[Math.floor(rng() * GRADE_HUES.length)] + (rng() - 0.5) * 20;
538
+ const intensity = 0.15 + rng() * 0.25;
539
+ return {
540
+ hue: (hue + 360) % 360,
541
+ intensity: intensity
542
+ };
543
+ }
429
544
 
430
545
 
431
546
 
@@ -1206,10 +1321,172 @@ const $dd5df256f00f6199$export$c2fc138f94dd4b2a = {
1206
1321
  };
1207
1322
 
1208
1323
 
1324
+ /**
1325
+ * Procedural shape generators — hash-derived shapes that are unique
1326
+ * per generation. Unlike the fixed shape library, these produce geometry
1327
+ * that doesn't repeat across hashes.
1328
+ *
1329
+ * All draw functions accept an RNG via the config parameter so the
1330
+ * shapes are deterministic from the hash.
1331
+ */ const $6222456bc073291c$export$580f80cfb9de73bc = (ctx, size, config)=>{
1332
+ const rng = config?.rng ?? Math.random;
1333
+ const r = size / 2;
1334
+ const numPoints = 5 + Math.floor(rng() * 5); // 5-9 lobes
1335
+ const points = [];
1336
+ for(let i = 0; i < numPoints; i++){
1337
+ const angle = i / numPoints * Math.PI * 2;
1338
+ const jitter = 0.5 + rng() * 0.5; // radius varies 50-100%
1339
+ points.push({
1340
+ x: Math.cos(angle) * r * jitter,
1341
+ y: Math.sin(angle) * r * jitter
1342
+ });
1343
+ }
1344
+ ctx.beginPath();
1345
+ // Start at midpoint between last and first point
1346
+ const last = points[points.length - 1];
1347
+ const first = points[0];
1348
+ ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
1349
+ for(let i = 0; i < numPoints; i++){
1350
+ const curr = points[i];
1351
+ const next = points[(i + 1) % numPoints];
1352
+ const midX = (curr.x + next.x) / 2;
1353
+ const midY = (curr.y + next.y) / 2;
1354
+ ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
1355
+ }
1356
+ ctx.closePath();
1357
+ };
1358
+ const $6222456bc073291c$export$7a6094023f0902a6 = (ctx, size, config)=>{
1359
+ const rng = config?.rng ?? Math.random;
1360
+ const r = size / 2;
1361
+ const sides = 3 + Math.floor(rng() * 10); // 3-12 sides
1362
+ const jitterAmount = 0.1 + rng() * 0.4; // 10-50% vertex displacement
1363
+ ctx.beginPath();
1364
+ for(let i = 0; i < sides; i++){
1365
+ const angle = i / sides * Math.PI * 2 - Math.PI / 2;
1366
+ const radiusJitter = 1 - jitterAmount + rng() * jitterAmount * 2;
1367
+ const x = Math.cos(angle) * r * radiusJitter;
1368
+ const y = Math.sin(angle) * r * radiusJitter;
1369
+ if (i === 0) ctx.moveTo(x, y);
1370
+ else ctx.lineTo(x, y);
1371
+ }
1372
+ ctx.closePath();
1373
+ };
1374
+ const $6222456bc073291c$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
1375
+ const rng = config?.rng ?? Math.random;
1376
+ const r = size / 2;
1377
+ // Frequency ratios — small integers produce recognizable patterns
1378
+ const freqA = 1 + Math.floor(rng() * 5); // 1-5
1379
+ const freqB = 1 + Math.floor(rng() * 5); // 1-5
1380
+ const phase = rng() * Math.PI; // phase offset
1381
+ const steps = 120;
1382
+ ctx.beginPath();
1383
+ for(let i = 0; i <= steps; i++){
1384
+ const t = i / steps * Math.PI * 2;
1385
+ const x = Math.sin(freqA * t + phase) * r;
1386
+ const y = Math.sin(freqB * t) * r;
1387
+ if (i === 0) ctx.moveTo(x, y);
1388
+ else ctx.lineTo(x, y);
1389
+ }
1390
+ ctx.closePath();
1391
+ };
1392
+ const $6222456bc073291c$export$1db9219b4f34658c = (ctx, size, config)=>{
1393
+ const rng = config?.rng ?? Math.random;
1394
+ const r = size / 2;
1395
+ // Exponent range: 0.3 (spiky astroid) to 5 (rounded rectangle)
1396
+ const n = 0.3 + rng() * 4.7;
1397
+ const steps = 120;
1398
+ ctx.beginPath();
1399
+ for(let i = 0; i <= steps; i++){
1400
+ const t = i / steps * Math.PI * 2;
1401
+ const cosT = Math.cos(t);
1402
+ const sinT = Math.sin(t);
1403
+ // Superellipse parametric form
1404
+ const x = Math.sign(cosT) * Math.pow(Math.abs(cosT), 2 / n) * r;
1405
+ const y = Math.sign(sinT) * Math.pow(Math.abs(sinT), 2 / n) * r;
1406
+ if (i === 0) ctx.moveTo(x, y);
1407
+ else ctx.lineTo(x, y);
1408
+ }
1409
+ ctx.closePath();
1410
+ };
1411
+ const $6222456bc073291c$export$b027c64d22b01985 = (ctx, size, config)=>{
1412
+ const rng = config?.rng ?? Math.random;
1413
+ const scale = size / 2;
1414
+ // R = outer radius, r = inner radius, d = pen distance from inner center
1415
+ const R = 1;
1416
+ const r = 0.2 + rng() * 0.6; // 0.2-0.8
1417
+ const d = 0.3 + rng() * 0.7; // 0.3-1.0
1418
+ // Number of full rotations needed to close the curve
1419
+ const gcd = (a, b)=>{
1420
+ const ai = Math.round(a * 1000);
1421
+ const bi = Math.round(b * 1000);
1422
+ const g = (x, y)=>y === 0 ? x : g(y, x % y);
1423
+ return g(ai, bi) / 1000;
1424
+ };
1425
+ const period = r / gcd(R, r);
1426
+ const maxT = Math.min(period, 10) * Math.PI * 2; // cap at 10 rotations
1427
+ const steps = Math.min(600, Math.floor(maxT * 20));
1428
+ ctx.beginPath();
1429
+ for(let i = 0; i <= steps; i++){
1430
+ const t = i / steps * maxT;
1431
+ const x = ((R - r) * Math.cos(t) + d * Math.cos((R - r) / r * t)) * scale / (1 + d);
1432
+ const y = ((R - r) * Math.sin(t) - d * Math.sin((R - r) / r * t)) * scale / (1 + d);
1433
+ if (i === 0) ctx.moveTo(x, y);
1434
+ else ctx.lineTo(x, y);
1435
+ }
1436
+ ctx.closePath();
1437
+ };
1438
+ const $6222456bc073291c$export$7608ccd03bfb705d = (ctx, size, config)=>{
1439
+ const rng = config?.rng ?? Math.random;
1440
+ const r = size / 2;
1441
+ const rings = 2 + Math.floor(rng() * 4); // 2-5 rings
1442
+ const freq = 3 + Math.floor(rng() * 12); // 3-14 waves per ring
1443
+ const amp = 0.05 + rng() * 0.15; // 5-20% of radius
1444
+ ctx.beginPath();
1445
+ for(let ring = 0; ring < rings; ring++){
1446
+ const baseR = r * (0.3 + ring / rings * 0.7);
1447
+ const steps = 80;
1448
+ for(let i = 0; i <= steps; i++){
1449
+ const t = i / steps * Math.PI * 2;
1450
+ const wave = Math.sin(t * freq + ring * 1.5) * baseR * amp;
1451
+ const x = Math.cos(t) * (baseR + wave);
1452
+ const y = Math.sin(t) * (baseR + wave);
1453
+ if (i === 0) ctx.moveTo(x, y);
1454
+ else ctx.lineTo(x, y);
1455
+ }
1456
+ }
1457
+ };
1458
+ const $6222456bc073291c$export$11a377e7498bb523 = (ctx, size, config)=>{
1459
+ const rng = config?.rng ?? Math.random;
1460
+ const r = size / 2;
1461
+ const k = 2 + Math.floor(rng() * 6); // 2-7 petal parameter
1462
+ const steps = 200;
1463
+ ctx.beginPath();
1464
+ for(let i = 0; i <= steps; i++){
1465
+ const theta = i / steps * Math.PI * 2 * (k % 2 === 0 ? 1 : 2);
1466
+ const rr = Math.cos(k * theta) * r;
1467
+ const x = rr * Math.cos(theta);
1468
+ const y = rr * Math.sin(theta);
1469
+ if (i === 0) ctx.moveTo(x, y);
1470
+ else ctx.lineTo(x, y);
1471
+ }
1472
+ ctx.closePath();
1473
+ };
1474
+ const $6222456bc073291c$export$40cfb4c637f2fbb5 = {
1475
+ blob: $6222456bc073291c$export$580f80cfb9de73bc,
1476
+ ngon: $6222456bc073291c$export$7a6094023f0902a6,
1477
+ lissajous: $6222456bc073291c$export$ef56b4a8316e47d5,
1478
+ superellipse: $6222456bc073291c$export$1db9219b4f34658c,
1479
+ spirograph: $6222456bc073291c$export$b027c64d22b01985,
1480
+ waveRing: $6222456bc073291c$export$7608ccd03bfb705d,
1481
+ rose: $6222456bc073291c$export$11a377e7498bb523
1482
+ };
1483
+
1484
+
1209
1485
  const $9c828bde2acaae64$export$4ff7fc6f1af248b5 = {
1210
1486
  ...(0, $42f4f729ab090d17$export$492753207a5258e1),
1211
1487
  ...(0, $4bf3d69be49ad55c$export$dbe318a13ce51887),
1212
- ...(0, $dd5df256f00f6199$export$c2fc138f94dd4b2a)
1488
+ ...(0, $dd5df256f00f6199$export$c2fc138f94dd4b2a),
1489
+ ...(0, $6222456bc073291c$export$40cfb4c637f2fbb5)
1213
1490
  };
1214
1491
 
1215
1492
 
@@ -1294,23 +1571,50 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
1294
1571
  break;
1295
1572
  case "watercolor":
1296
1573
  {
1297
- // Draw 3-4 slightly offset passes at low opacity for a bleed effect
1298
- const passes = 3 + (rng ? Math.floor(rng() * 2) : 0);
1574
+ // Improved watercolor: edge darkening + radial bleed + layered washes
1575
+ const passes = 4 + (rng ? Math.floor(rng() * 2) : 0);
1299
1576
  const savedAlpha = ctx.globalAlpha;
1300
- ctx.globalAlpha = savedAlpha * (0.3 / passes * 2);
1577
+ // Pass 1: Base wash large, soft fill at low opacity
1578
+ ctx.globalAlpha = savedAlpha * 0.15;
1579
+ ctx.save();
1580
+ const baseScale = 1.08 + (rng ? rng() * 0.04 : 0);
1581
+ ctx.scale(baseScale, baseScale);
1582
+ ctx.fill();
1583
+ ctx.restore();
1584
+ // Pass 2: Multiple offset washes with radial displacement
1585
+ ctx.globalAlpha = savedAlpha * (0.25 / passes * 2);
1301
1586
  for(let p = 0; p < passes; p++){
1302
- const jx = rng ? (rng() - 0.5) * size * 0.06 : 0;
1303
- const jy = rng ? (rng() - 0.5) * size * 0.06 : 0;
1587
+ // Radial outward displacement (not uniform) for organic bleed
1588
+ const angle = rng ? rng() * Math.PI * 2 : p * Math.PI / 2;
1589
+ const dist = rng ? rng() * size * 0.05 : size * 0.02;
1590
+ const jx = Math.cos(angle) * dist;
1591
+ const jy = Math.sin(angle) * dist;
1304
1592
  ctx.save();
1305
1593
  ctx.translate(jx, jy);
1306
1594
  ctx.fill();
1307
1595
  ctx.restore();
1308
1596
  }
1597
+ // Pass 3: Edge darkening — draw a slightly smaller shape with lighter fill
1598
+ // to simulate pigment pooling at boundaries
1599
+ ctx.globalAlpha = savedAlpha * 0.35;
1600
+ ctx.save();
1601
+ const innerScale = 0.85 + (rng ? rng() * 0.08 : 0);
1602
+ ctx.scale(innerScale, innerScale);
1603
+ // Lighten the fill for the inner area
1604
+ const origFill = ctx.fillStyle;
1605
+ if (typeof fillColor === "string") ctx.fillStyle = fillColor.replace(/[\d.]+\)$/, (m)=>{
1606
+ const v = parseFloat(m);
1607
+ return Math.min(1, v * 1.4).toFixed(2) + ")";
1608
+ });
1609
+ ctx.fill();
1610
+ ctx.fillStyle = origFill;
1611
+ ctx.restore();
1309
1612
  ctx.globalAlpha = savedAlpha;
1310
- // Light stroke on top
1311
- ctx.globalAlpha *= 0.4;
1613
+ // Soft stroke on top — thinner than normal for delicacy
1614
+ ctx.globalAlpha *= 0.25;
1615
+ ctx.lineWidth = strokeWidth * 0.6;
1312
1616
  ctx.stroke();
1313
- ctx.globalAlpha /= 0.4;
1617
+ ctx.globalAlpha /= 0.25;
1314
1618
  break;
1315
1619
  }
1316
1620
  case "hatched":
@@ -1413,7 +1717,9 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
1413
1717
  ctx.lineWidth = strokeWidth;
1414
1718
  const drawFunction = (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[shape];
1415
1719
  if (drawFunction) {
1416
- drawFunction(ctx, size);
1720
+ drawFunction(ctx, size, {
1721
+ rng: rng
1722
+ });
1417
1723
  $c3de8257a8baa3b0$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
1418
1724
  }
1419
1725
  // Reset shadow so patterns aren't double-glowed
@@ -1430,6 +1736,675 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
1430
1736
 
1431
1737
 
1432
1738
 
1739
+ /**
1740
+ * Shape affinity system — controls which shapes look good together,
1741
+ * quality tiers for different rendering contexts, and size preferences.
1742
+ *
1743
+ * This replaces the naive "pick any shape" approach with intentional
1744
+ * curation that produces more cohesive compositions.
1745
+ */ // ── Quality tiers ───────────────────────────────────────────────────
1746
+ // Not all shapes render equally well at all sizes or in all contexts.
1747
+ // Tier 1 shapes are visually strong at any size; Tier 3 shapes need
1748
+ // specific conditions to look good.
1749
+ const $e73976f898150d4d$export$4343b39fe47bd82c = {
1750
+ // ── Basic shapes ──────────────────────────────────────────────
1751
+ circle: {
1752
+ tier: 1,
1753
+ minSizeFraction: 0.05,
1754
+ maxSizeFraction: 1.0,
1755
+ affinities: [
1756
+ "circle",
1757
+ "blob",
1758
+ "hexagon",
1759
+ "flowerOfLife",
1760
+ "seedOfLife"
1761
+ ],
1762
+ category: "basic",
1763
+ heroCandidate: false,
1764
+ bestStyles: [
1765
+ "fill-only",
1766
+ "watercolor",
1767
+ "fill-and-stroke"
1768
+ ]
1769
+ },
1770
+ square: {
1771
+ tier: 2,
1772
+ minSizeFraction: 0.08,
1773
+ maxSizeFraction: 0.7,
1774
+ affinities: [
1775
+ "square",
1776
+ "diamond",
1777
+ "superellipse",
1778
+ "islamicPattern"
1779
+ ],
1780
+ category: "basic",
1781
+ heroCandidate: false,
1782
+ bestStyles: [
1783
+ "fill-and-stroke",
1784
+ "stroke-only",
1785
+ "hatched"
1786
+ ]
1787
+ },
1788
+ triangle: {
1789
+ tier: 1,
1790
+ minSizeFraction: 0.06,
1791
+ maxSizeFraction: 0.9,
1792
+ affinities: [
1793
+ "triangle",
1794
+ "diamond",
1795
+ "hexagon",
1796
+ "merkaba",
1797
+ "sriYantra"
1798
+ ],
1799
+ category: "basic",
1800
+ heroCandidate: false,
1801
+ bestStyles: [
1802
+ "fill-and-stroke",
1803
+ "fill-only",
1804
+ "watercolor"
1805
+ ]
1806
+ },
1807
+ hexagon: {
1808
+ tier: 1,
1809
+ minSizeFraction: 0.05,
1810
+ maxSizeFraction: 1.0,
1811
+ affinities: [
1812
+ "hexagon",
1813
+ "circle",
1814
+ "flowerOfLife",
1815
+ "metatronsCube",
1816
+ "triangle"
1817
+ ],
1818
+ category: "basic",
1819
+ heroCandidate: false,
1820
+ bestStyles: [
1821
+ "fill-only",
1822
+ "fill-and-stroke",
1823
+ "watercolor"
1824
+ ]
1825
+ },
1826
+ star: {
1827
+ tier: 2,
1828
+ minSizeFraction: 0.08,
1829
+ maxSizeFraction: 0.6,
1830
+ affinities: [
1831
+ "star",
1832
+ "circle",
1833
+ "mandala",
1834
+ "spirograph"
1835
+ ],
1836
+ category: "basic",
1837
+ heroCandidate: false,
1838
+ bestStyles: [
1839
+ "fill-and-stroke",
1840
+ "stroke-only",
1841
+ "dashed"
1842
+ ]
1843
+ },
1844
+ "jacked-star": {
1845
+ tier: 3,
1846
+ minSizeFraction: 0.1,
1847
+ maxSizeFraction: 0.4,
1848
+ affinities: [
1849
+ "star",
1850
+ "circle"
1851
+ ],
1852
+ category: "basic",
1853
+ heroCandidate: false,
1854
+ bestStyles: [
1855
+ "stroke-only",
1856
+ "dashed"
1857
+ ]
1858
+ },
1859
+ heart: {
1860
+ tier: 3,
1861
+ minSizeFraction: 0.1,
1862
+ maxSizeFraction: 0.5,
1863
+ affinities: [
1864
+ "circle",
1865
+ "blob"
1866
+ ],
1867
+ category: "basic",
1868
+ heroCandidate: false,
1869
+ bestStyles: [
1870
+ "fill-only",
1871
+ "watercolor"
1872
+ ]
1873
+ },
1874
+ diamond: {
1875
+ tier: 2,
1876
+ minSizeFraction: 0.06,
1877
+ maxSizeFraction: 0.8,
1878
+ affinities: [
1879
+ "diamond",
1880
+ "triangle",
1881
+ "square",
1882
+ "merkaba"
1883
+ ],
1884
+ category: "basic",
1885
+ heroCandidate: false,
1886
+ bestStyles: [
1887
+ "fill-and-stroke",
1888
+ "fill-only",
1889
+ "double-stroke"
1890
+ ]
1891
+ },
1892
+ cube: {
1893
+ tier: 3,
1894
+ minSizeFraction: 0.08,
1895
+ maxSizeFraction: 0.5,
1896
+ affinities: [
1897
+ "square",
1898
+ "diamond"
1899
+ ],
1900
+ category: "basic",
1901
+ heroCandidate: false,
1902
+ bestStyles: [
1903
+ "stroke-only",
1904
+ "fill-and-stroke"
1905
+ ]
1906
+ },
1907
+ // ── Complex shapes ────────────────────────────────────────────
1908
+ platonicSolid: {
1909
+ tier: 2,
1910
+ minSizeFraction: 0.15,
1911
+ maxSizeFraction: 0.8,
1912
+ affinities: [
1913
+ "metatronsCube",
1914
+ "merkaba",
1915
+ "hexagon",
1916
+ "triangle"
1917
+ ],
1918
+ category: "complex",
1919
+ heroCandidate: true,
1920
+ bestStyles: [
1921
+ "stroke-only",
1922
+ "double-stroke",
1923
+ "dashed"
1924
+ ]
1925
+ },
1926
+ fibonacciSpiral: {
1927
+ tier: 1,
1928
+ minSizeFraction: 0.2,
1929
+ maxSizeFraction: 1.0,
1930
+ affinities: [
1931
+ "circle",
1932
+ "rose",
1933
+ "spirograph",
1934
+ "flowerOfLife"
1935
+ ],
1936
+ category: "complex",
1937
+ heroCandidate: true,
1938
+ bestStyles: [
1939
+ "stroke-only",
1940
+ "incomplete",
1941
+ "watercolor"
1942
+ ]
1943
+ },
1944
+ islamicPattern: {
1945
+ tier: 2,
1946
+ minSizeFraction: 0.25,
1947
+ maxSizeFraction: 0.9,
1948
+ affinities: [
1949
+ "square",
1950
+ "hexagon",
1951
+ "star",
1952
+ "mandala"
1953
+ ],
1954
+ category: "complex",
1955
+ heroCandidate: true,
1956
+ bestStyles: [
1957
+ "stroke-only",
1958
+ "dashed",
1959
+ "hatched"
1960
+ ]
1961
+ },
1962
+ celticKnot: {
1963
+ tier: 2,
1964
+ minSizeFraction: 0.2,
1965
+ maxSizeFraction: 0.7,
1966
+ affinities: [
1967
+ "circle",
1968
+ "lissajous",
1969
+ "spirograph"
1970
+ ],
1971
+ category: "complex",
1972
+ heroCandidate: true,
1973
+ bestStyles: [
1974
+ "stroke-only",
1975
+ "double-stroke"
1976
+ ]
1977
+ },
1978
+ merkaba: {
1979
+ tier: 1,
1980
+ minSizeFraction: 0.15,
1981
+ maxSizeFraction: 1.0,
1982
+ affinities: [
1983
+ "triangle",
1984
+ "diamond",
1985
+ "sriYantra",
1986
+ "metatronsCube"
1987
+ ],
1988
+ category: "complex",
1989
+ heroCandidate: true,
1990
+ bestStyles: [
1991
+ "stroke-only",
1992
+ "fill-and-stroke",
1993
+ "double-stroke"
1994
+ ]
1995
+ },
1996
+ mandala: {
1997
+ tier: 1,
1998
+ minSizeFraction: 0.2,
1999
+ maxSizeFraction: 1.0,
2000
+ affinities: [
2001
+ "circle",
2002
+ "flowerOfLife",
2003
+ "spirograph",
2004
+ "rose"
2005
+ ],
2006
+ category: "complex",
2007
+ heroCandidate: true,
2008
+ bestStyles: [
2009
+ "stroke-only",
2010
+ "dashed",
2011
+ "incomplete"
2012
+ ]
2013
+ },
2014
+ fractal: {
2015
+ tier: 2,
2016
+ minSizeFraction: 0.2,
2017
+ maxSizeFraction: 0.8,
2018
+ affinities: [
2019
+ "blob",
2020
+ "lissajous",
2021
+ "circle"
2022
+ ],
2023
+ category: "complex",
2024
+ heroCandidate: true,
2025
+ bestStyles: [
2026
+ "stroke-only",
2027
+ "incomplete"
2028
+ ]
2029
+ },
2030
+ // ── Sacred shapes ─────────────────────────────────────────────
2031
+ flowerOfLife: {
2032
+ tier: 1,
2033
+ minSizeFraction: 0.2,
2034
+ maxSizeFraction: 1.0,
2035
+ affinities: [
2036
+ "circle",
2037
+ "hexagon",
2038
+ "seedOfLife",
2039
+ "eggOfLife",
2040
+ "metatronsCube"
2041
+ ],
2042
+ category: "sacred",
2043
+ heroCandidate: true,
2044
+ bestStyles: [
2045
+ "stroke-only",
2046
+ "watercolor",
2047
+ "incomplete"
2048
+ ]
2049
+ },
2050
+ treeOfLife: {
2051
+ tier: 2,
2052
+ minSizeFraction: 0.25,
2053
+ maxSizeFraction: 0.9,
2054
+ affinities: [
2055
+ "circle",
2056
+ "flowerOfLife",
2057
+ "metatronsCube"
2058
+ ],
2059
+ category: "sacred",
2060
+ heroCandidate: true,
2061
+ bestStyles: [
2062
+ "stroke-only",
2063
+ "double-stroke"
2064
+ ]
2065
+ },
2066
+ metatronsCube: {
2067
+ tier: 1,
2068
+ minSizeFraction: 0.2,
2069
+ maxSizeFraction: 1.0,
2070
+ affinities: [
2071
+ "hexagon",
2072
+ "flowerOfLife",
2073
+ "platonicSolid",
2074
+ "merkaba"
2075
+ ],
2076
+ category: "sacred",
2077
+ heroCandidate: true,
2078
+ bestStyles: [
2079
+ "stroke-only",
2080
+ "dashed",
2081
+ "incomplete"
2082
+ ]
2083
+ },
2084
+ sriYantra: {
2085
+ tier: 1,
2086
+ minSizeFraction: 0.2,
2087
+ maxSizeFraction: 1.0,
2088
+ affinities: [
2089
+ "triangle",
2090
+ "merkaba",
2091
+ "mandala",
2092
+ "diamond"
2093
+ ],
2094
+ category: "sacred",
2095
+ heroCandidate: true,
2096
+ bestStyles: [
2097
+ "stroke-only",
2098
+ "fill-and-stroke",
2099
+ "double-stroke"
2100
+ ]
2101
+ },
2102
+ seedOfLife: {
2103
+ tier: 1,
2104
+ minSizeFraction: 0.15,
2105
+ maxSizeFraction: 0.9,
2106
+ affinities: [
2107
+ "circle",
2108
+ "flowerOfLife",
2109
+ "eggOfLife",
2110
+ "hexagon"
2111
+ ],
2112
+ category: "sacred",
2113
+ heroCandidate: true,
2114
+ bestStyles: [
2115
+ "stroke-only",
2116
+ "watercolor",
2117
+ "fill-only"
2118
+ ]
2119
+ },
2120
+ vesicaPiscis: {
2121
+ tier: 2,
2122
+ minSizeFraction: 0.15,
2123
+ maxSizeFraction: 0.7,
2124
+ affinities: [
2125
+ "circle",
2126
+ "seedOfLife",
2127
+ "flowerOfLife"
2128
+ ],
2129
+ category: "sacred",
2130
+ heroCandidate: false,
2131
+ bestStyles: [
2132
+ "stroke-only",
2133
+ "watercolor"
2134
+ ]
2135
+ },
2136
+ torus: {
2137
+ tier: 3,
2138
+ minSizeFraction: 0.2,
2139
+ maxSizeFraction: 0.6,
2140
+ affinities: [
2141
+ "circle",
2142
+ "spirograph",
2143
+ "waveRing"
2144
+ ],
2145
+ category: "sacred",
2146
+ heroCandidate: false,
2147
+ bestStyles: [
2148
+ "stroke-only",
2149
+ "dashed"
2150
+ ]
2151
+ },
2152
+ eggOfLife: {
2153
+ tier: 2,
2154
+ minSizeFraction: 0.15,
2155
+ maxSizeFraction: 0.8,
2156
+ affinities: [
2157
+ "circle",
2158
+ "seedOfLife",
2159
+ "flowerOfLife"
2160
+ ],
2161
+ category: "sacred",
2162
+ heroCandidate: true,
2163
+ bestStyles: [
2164
+ "stroke-only",
2165
+ "watercolor"
2166
+ ]
2167
+ },
2168
+ // ── Procedural shapes ─────────────────────────────────────────
2169
+ blob: {
2170
+ tier: 1,
2171
+ minSizeFraction: 0.05,
2172
+ maxSizeFraction: 1.0,
2173
+ affinities: [
2174
+ "blob",
2175
+ "circle",
2176
+ "superellipse",
2177
+ "waveRing"
2178
+ ],
2179
+ category: "procedural",
2180
+ heroCandidate: false,
2181
+ bestStyles: [
2182
+ "fill-only",
2183
+ "watercolor",
2184
+ "fill-and-stroke"
2185
+ ]
2186
+ },
2187
+ ngon: {
2188
+ tier: 2,
2189
+ minSizeFraction: 0.06,
2190
+ maxSizeFraction: 0.8,
2191
+ affinities: [
2192
+ "hexagon",
2193
+ "triangle",
2194
+ "diamond",
2195
+ "superellipse"
2196
+ ],
2197
+ category: "procedural",
2198
+ heroCandidate: false,
2199
+ bestStyles: [
2200
+ "fill-and-stroke",
2201
+ "fill-only",
2202
+ "hatched"
2203
+ ]
2204
+ },
2205
+ lissajous: {
2206
+ tier: 2,
2207
+ minSizeFraction: 0.15,
2208
+ maxSizeFraction: 0.8,
2209
+ affinities: [
2210
+ "spirograph",
2211
+ "rose",
2212
+ "celticKnot",
2213
+ "fibonacciSpiral"
2214
+ ],
2215
+ category: "procedural",
2216
+ heroCandidate: false,
2217
+ bestStyles: [
2218
+ "stroke-only",
2219
+ "incomplete",
2220
+ "dashed"
2221
+ ]
2222
+ },
2223
+ superellipse: {
2224
+ tier: 1,
2225
+ minSizeFraction: 0.05,
2226
+ maxSizeFraction: 1.0,
2227
+ affinities: [
2228
+ "circle",
2229
+ "square",
2230
+ "blob",
2231
+ "hexagon"
2232
+ ],
2233
+ category: "procedural",
2234
+ heroCandidate: false,
2235
+ bestStyles: [
2236
+ "fill-only",
2237
+ "watercolor",
2238
+ "fill-and-stroke"
2239
+ ]
2240
+ },
2241
+ spirograph: {
2242
+ tier: 1,
2243
+ minSizeFraction: 0.15,
2244
+ maxSizeFraction: 0.9,
2245
+ affinities: [
2246
+ "rose",
2247
+ "lissajous",
2248
+ "mandala",
2249
+ "flowerOfLife"
2250
+ ],
2251
+ category: "procedural",
2252
+ heroCandidate: true,
2253
+ bestStyles: [
2254
+ "stroke-only",
2255
+ "incomplete",
2256
+ "dashed"
2257
+ ]
2258
+ },
2259
+ waveRing: {
2260
+ tier: 2,
2261
+ minSizeFraction: 0.1,
2262
+ maxSizeFraction: 0.8,
2263
+ affinities: [
2264
+ "circle",
2265
+ "blob",
2266
+ "torus",
2267
+ "spirograph"
2268
+ ],
2269
+ category: "procedural",
2270
+ heroCandidate: false,
2271
+ bestStyles: [
2272
+ "stroke-only",
2273
+ "dashed",
2274
+ "incomplete"
2275
+ ]
2276
+ },
2277
+ rose: {
2278
+ tier: 1,
2279
+ minSizeFraction: 0.1,
2280
+ maxSizeFraction: 0.9,
2281
+ affinities: [
2282
+ "spirograph",
2283
+ "mandala",
2284
+ "flowerOfLife",
2285
+ "circle"
2286
+ ],
2287
+ category: "procedural",
2288
+ heroCandidate: true,
2289
+ bestStyles: [
2290
+ "stroke-only",
2291
+ "fill-only",
2292
+ "watercolor"
2293
+ ]
2294
+ }
2295
+ };
2296
+ function $e73976f898150d4d$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
2297
+ const available = shapeNames.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]);
2298
+ // Pick a seed shape — tier 1 shapes that are hero candidates
2299
+ const heroPool = available.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s].tier === 1 && $e73976f898150d4d$export$4343b39fe47bd82c[s].heroCandidate);
2300
+ const seedShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : available[Math.floor(rng() * available.length)];
2301
+ const seedProfile = $e73976f898150d4d$export$4343b39fe47bd82c[seedShape];
2302
+ // Primary: seed shape + its direct affinities (tier 1-2 only)
2303
+ const primaryCandidates = [
2304
+ seedShape,
2305
+ ...seedProfile.affinities
2306
+ ].filter((s)=>available.includes(s)).filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s].tier <= 2);
2307
+ const primary = [
2308
+ ...new Set(primaryCandidates)
2309
+ ].slice(0, 5);
2310
+ // Supporting: affinities of affinities, plus same-category shapes
2311
+ const supportingSet = new Set();
2312
+ for (const p of primary){
2313
+ const profile = $e73976f898150d4d$export$4343b39fe47bd82c[p];
2314
+ if (!profile) continue;
2315
+ for (const aff of profile.affinities)if (available.includes(aff) && !primary.includes(aff)) supportingSet.add(aff);
2316
+ }
2317
+ // Add same-category tier 1-2 shapes
2318
+ for (const s of available){
2319
+ const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
2320
+ if (p.category === seedProfile.category && p.tier <= 2 && !primary.includes(s)) supportingSet.add(s);
2321
+ }
2322
+ const supporting = [
2323
+ ...supportingSet
2324
+ ].slice(0, 6);
2325
+ // Accents: tier 1 shapes from other categories for contrast
2326
+ const usedCategories = new Set([
2327
+ ...primary,
2328
+ ...supporting
2329
+ ].map((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]?.category));
2330
+ const accentCandidates = available.filter((s)=>!primary.includes(s) && !supporting.includes(s) && $e73976f898150d4d$export$4343b39fe47bd82c[s].tier <= 2 && !usedCategories.has($e73976f898150d4d$export$4343b39fe47bd82c[s].category));
2331
+ // Shuffle and take a few
2332
+ const accents = [];
2333
+ const shuffled = [
2334
+ ...accentCandidates
2335
+ ];
2336
+ for(let i = shuffled.length - 1; i > 0; i--){
2337
+ const j = Math.floor(rng() * (i + 1));
2338
+ [shuffled[i], shuffled[j]] = [
2339
+ shuffled[j],
2340
+ shuffled[i]
2341
+ ];
2342
+ }
2343
+ accents.push(...shuffled.slice(0, 3));
2344
+ // For certain archetypes, bias the palette
2345
+ if (archetypeName === "geometric-precision") // Remove blobs and organic shapes from primary
2346
+ return {
2347
+ primary: primary.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]?.category !== "procedural" || s === "ngon"),
2348
+ supporting: supporting.filter((s)=>s !== "blob"),
2349
+ accents: accents
2350
+ };
2351
+ if (archetypeName === "organic-flow") {
2352
+ // Boost procedural/organic shapes
2353
+ const organicBoost = available.filter((s)=>[
2354
+ "blob",
2355
+ "superellipse",
2356
+ "waveRing",
2357
+ "rose"
2358
+ ].includes(s) && !primary.includes(s));
2359
+ return {
2360
+ primary: [
2361
+ ...primary,
2362
+ ...organicBoost.slice(0, 2)
2363
+ ],
2364
+ supporting: supporting,
2365
+ accents: accents
2366
+ };
2367
+ }
2368
+ return {
2369
+ primary: primary,
2370
+ supporting: supporting,
2371
+ accents: accents
2372
+ };
2373
+ }
2374
+ function $e73976f898150d4d$export$3c37d9a045754d0e(palette, rng, sizeFraction) {
2375
+ // Filter each tier by size constraints
2376
+ const validPrimary = palette.primary.filter((s)=>{
2377
+ const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
2378
+ return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
2379
+ });
2380
+ const validSupporting = palette.supporting.filter((s)=>{
2381
+ const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
2382
+ return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
2383
+ });
2384
+ const validAccents = palette.accents.filter((s)=>{
2385
+ const p = $e73976f898150d4d$export$4343b39fe47bd82c[s];
2386
+ return p && sizeFraction >= p.minSizeFraction && sizeFraction <= p.maxSizeFraction;
2387
+ });
2388
+ const roll = rng();
2389
+ if (roll < 0.60 && validPrimary.length > 0) return validPrimary[Math.floor(rng() * validPrimary.length)];
2390
+ if (roll < 0.90 && validSupporting.length > 0) return validSupporting[Math.floor(rng() * validSupporting.length)];
2391
+ if (validAccents.length > 0) return validAccents[Math.floor(rng() * validAccents.length)];
2392
+ // Fallback: any valid primary or supporting
2393
+ const fallback = [
2394
+ ...validPrimary,
2395
+ ...validSupporting
2396
+ ];
2397
+ if (fallback.length > 0) return fallback[Math.floor(rng() * fallback.length)];
2398
+ // Ultimate fallback
2399
+ return palette.primary[0] || "circle";
2400
+ }
2401
+ function $e73976f898150d4d$export$ab873bb6fb56c1a8(shapeName, layerStyle, rng) {
2402
+ const profile = $e73976f898150d4d$export$4343b39fe47bd82c[shapeName];
2403
+ if (!profile || rng() > 0.7) return layerStyle;
2404
+ return profile.bestStyles[Math.floor(rng() * profile.bestStyles.length)];
2405
+ }
2406
+
2407
+
1433
2408
 
1434
2409
  /**
1435
2410
  * Configuration options for image generation.
@@ -1644,6 +2619,69 @@ const $f89bc858f7202849$var$ARCHETYPES = [
1644
2619
  sizePower: 2.5,
1645
2620
  invertForeground: false
1646
2621
  },
2622
+ {
2623
+ name: "watercolor-wash",
2624
+ gridSize: 3,
2625
+ layers: 3,
2626
+ baseOpacity: 0.25,
2627
+ opacityReduction: 0.03,
2628
+ minShapeSize: 200,
2629
+ maxShapeSize: 700,
2630
+ backgroundStyle: "radial-light",
2631
+ paletteMode: "harmonious",
2632
+ preferredStyles: [
2633
+ "watercolor",
2634
+ "fill-only",
2635
+ "incomplete"
2636
+ ],
2637
+ flowLineMultiplier: 0.5,
2638
+ heroShape: false,
2639
+ glowMultiplier: 0.3,
2640
+ sizePower: 0.6,
2641
+ invertForeground: false
2642
+ },
2643
+ {
2644
+ name: "op-art",
2645
+ gridSize: 8,
2646
+ layers: 2,
2647
+ baseOpacity: 0.95,
2648
+ opacityReduction: 0.05,
2649
+ minShapeSize: 20,
2650
+ maxShapeSize: 200,
2651
+ backgroundStyle: "solid-light",
2652
+ paletteMode: "high-contrast",
2653
+ preferredStyles: [
2654
+ "fill-and-stroke",
2655
+ "stroke-only",
2656
+ "dashed"
2657
+ ],
2658
+ flowLineMultiplier: 0,
2659
+ heroShape: false,
2660
+ glowMultiplier: 0,
2661
+ sizePower: 0.4,
2662
+ invertForeground: false
2663
+ },
2664
+ {
2665
+ name: "collage",
2666
+ gridSize: 4,
2667
+ layers: 3,
2668
+ baseOpacity: 0.9,
2669
+ opacityReduction: 0.08,
2670
+ minShapeSize: 80,
2671
+ maxShapeSize: 500,
2672
+ backgroundStyle: "solid-light",
2673
+ paletteMode: "duotone",
2674
+ preferredStyles: [
2675
+ "fill-and-stroke",
2676
+ "fill-only",
2677
+ "double-stroke"
2678
+ ],
2679
+ flowLineMultiplier: 0,
2680
+ heroShape: true,
2681
+ glowMultiplier: 0,
2682
+ sizePower: 0.7,
2683
+ invertForeground: false
2684
+ },
1647
2685
  {
1648
2686
  name: "classic",
1649
2687
  gridSize: 5,
@@ -1671,26 +2709,7 @@ function $f89bc858f7202849$export$f1142fd7da4d6590(rng) {
1671
2709
  }
1672
2710
 
1673
2711
 
1674
- // ── Shape categories for weighted selection ─────────────────────────
1675
- const $4f72c5a314eddf25$var$BASIC_SHAPES = [
1676
- "circle",
1677
- "square",
1678
- "triangle",
1679
- "hexagon",
1680
- "diamond",
1681
- "cube"
1682
- ];
1683
- const $4f72c5a314eddf25$var$COMPLEX_SHAPES = [
1684
- "star",
1685
- "jacked-star",
1686
- "heart",
1687
- "platonicSolid",
1688
- "fibonacciSpiral",
1689
- "islamicPattern",
1690
- "celticKnot",
1691
- "merkaba",
1692
- "fractal"
1693
- ];
2712
+ // ── Shape categories for weighted selection (legacy fallback) ───────
1694
2713
  const $4f72c5a314eddf25$var$SACRED_SHAPES = [
1695
2714
  "mandala",
1696
2715
  "flowerOfLife",
@@ -1709,21 +2728,6 @@ const $4f72c5a314eddf25$var$COMPOSITION_MODES = [
1709
2728
  "grid-subdivision",
1710
2729
  "clustered"
1711
2730
  ];
1712
- // ── Helper: pick shape with layer-aware weighting ───────────────────
1713
- function $4f72c5a314eddf25$var$pickShape(rng, layerRatio, shapeNames) {
1714
- const basicW = 1 - layerRatio * 0.6;
1715
- const complexW = 0.3 + layerRatio * 0.3;
1716
- const sacredW = 0.1 + layerRatio * 0.4;
1717
- const total = basicW + complexW + sacredW;
1718
- const roll = rng() * total;
1719
- let pool;
1720
- if (roll < basicW) pool = $4f72c5a314eddf25$var$BASIC_SHAPES;
1721
- else if (roll < basicW + complexW) pool = $4f72c5a314eddf25$var$COMPLEX_SHAPES;
1722
- else pool = $4f72c5a314eddf25$var$SACRED_SHAPES;
1723
- const available = pool.filter((s)=>shapeNames.includes(s));
1724
- if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
1725
- return available[Math.floor(rng() * available.length)];
1726
- }
1727
2731
  // ── Helper: get position based on composition mode ──────────────────
1728
2732
  function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
1729
2733
  switch(mode){
@@ -1782,22 +2786,28 @@ function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height,
1782
2786
  };
1783
2787
  }
1784
2788
  }
1785
- // ── Helper: positional color blending ───────────────────────────────
1786
- function $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, colors, rng) {
1787
- const nx = x / width;
1788
- const ny = y / height;
1789
- const posIndex = (nx * 0.6 + ny * 0.4) * (colors.length - 1);
1790
- const baseIdx = Math.floor(posIndex) % colors.length;
1791
- return (0, $d016ad53434219a1$export$59539d800dbe6858)(colors[baseIdx], rng, 0.08);
2789
+ // ── Helper: positional color from hierarchy ─────────────────────────
2790
+ function $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, hierarchy, rng) {
2791
+ // Blend position into color selection — shapes near center lean dominant
2792
+ const distFromCenter = Math.hypot(x - width / 2, y - height / 2) / Math.hypot(width / 2, height / 2);
2793
+ // Center = more dominant, edges = more accent
2794
+ if (distFromCenter < 0.35) return (0, $d016ad53434219a1$export$18a34c25ea7e724b)(hierarchy.dominant, rng, 10, 0.08);
2795
+ else if (distFromCenter < 0.7) return (0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(hierarchy, rng), rng, 8, 0.06);
2796
+ else {
2797
+ // Edges: bias toward secondary/accent
2798
+ const roll = rng();
2799
+ const color = roll < 0.4 ? hierarchy.secondary : roll < 0.75 ? hierarchy.accent : hierarchy.dominant;
2800
+ return (0, $d016ad53434219a1$export$18a34c25ea7e724b)(color, rng, 12, 0.08);
2801
+ }
1792
2802
  }
1793
- // ── Helper: check if a position is inside a void zone (Feature E) ───
2803
+ // ── Helper: check if a position is inside a void zone ───────────────
1794
2804
  function $4f72c5a314eddf25$var$isInVoidZone(x, y, voidZones) {
1795
2805
  for (const zone of voidZones){
1796
2806
  if (Math.hypot(x - zone.x, y - zone.y) < zone.radius) return true;
1797
2807
  }
1798
2808
  return false;
1799
2809
  }
1800
- // ── Helper: density check for negative space (Feature E) ────────────
2810
+ // ── Helper: density check ───────────────────────────────────────────
1801
2811
  function $4f72c5a314eddf25$var$localDensity(x, y, positions, radius) {
1802
2812
  let count = 0;
1803
2813
  for (const p of positions)if (Math.hypot(x - p.x, y - p.y) < radius) count++;
@@ -1893,7 +2903,15 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
1893
2903
  const [bgStart, bgEnd] = colorScheme.getBackgroundColorsByMode(archetype.paletteMode);
1894
2904
  const tempMode = colorScheme.getTemperatureMode();
1895
2905
  const fgTempTarget = tempMode === "warm-bg" ? "cool" : tempMode === "cool-bg" ? "warm" : null;
2906
+ // ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
2907
+ const colorHierarchy = (0, $d016ad53434219a1$export$fabac4600b87056)(colors, rng);
2908
+ // ── 0c. Shape palette — curated shapes that work well together ──
1896
2909
  const shapeNames = Object.keys((0, $9c828bde2acaae64$export$4ff7fc6f1af248b5));
2910
+ const shapePalette = (0, $e73976f898150d4d$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
2911
+ // ── 0d. Color grading — unified tone for the whole image ───────
2912
+ const colorGrade = (0, $d016ad53434219a1$export$6d1620b367f86f7a)(rng);
2913
+ // ── 0e. Light direction — consistent shadow angle ──────────────
2914
+ const lightAngle = rng() * Math.PI * 2;
1897
2915
  const scaleFactor = Math.min(width, height) / 1024;
1898
2916
  const adjustedMinSize = minShapeSize * scaleFactor;
1899
2917
  const adjustedMaxSize = maxShapeSize * scaleFactor;
@@ -1902,25 +2920,45 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
1902
2920
  // ── 1. Background ──────────────────────────────────────────────
1903
2921
  const bgRadius = Math.hypot(cx, cy);
1904
2922
  $4f72c5a314eddf25$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
1905
- // ── 1b. Layered background (Feature G) ─────────────────────────
1906
- // Draw large, very faint shapes to give the background texture
2923
+ // Gradient mesh overlay 3-4 color control points for richer backgrounds
2924
+ const meshPoints = 3 + Math.floor(rng() * 2);
2925
+ ctx.globalCompositeOperation = "soft-light";
2926
+ for(let i = 0; i < meshPoints; i++){
2927
+ const mx = rng() * width;
2928
+ const my = rng() * height;
2929
+ const mRadius = Math.min(width, height) * (0.3 + rng() * 0.4);
2930
+ const mColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
2931
+ const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
2932
+ grad.addColorStop(0, (0, $d016ad53434219a1$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
2933
+ grad.addColorStop(1, "rgba(0,0,0,0)");
2934
+ ctx.globalAlpha = 1;
2935
+ ctx.fillStyle = grad;
2936
+ ctx.fillRect(0, 0, width, height);
2937
+ }
2938
+ ctx.globalCompositeOperation = "source-over";
2939
+ // Compute average background luminance for contrast enforcement
2940
+ const bgLum = ((0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
2941
+ // ── 1b. Layered background — archetype-coherent shapes ─────────
1907
2942
  const bgShapeCount = 3 + Math.floor(rng() * 4);
1908
2943
  ctx.globalCompositeOperation = "soft-light";
1909
2944
  for(let i = 0; i < bgShapeCount; i++){
1910
2945
  const bx = rng() * width;
1911
2946
  const by = rng() * height;
1912
2947
  const bSize = width * 0.3 + rng() * width * 0.5;
1913
- const bColor = colors[Math.floor(rng() * colors.length)];
2948
+ const bColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
1914
2949
  ctx.globalAlpha = 0.03 + rng() * 0.05;
1915
2950
  ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(bColor, 0.15);
1916
2951
  ctx.beginPath();
1917
- ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
2952
+ // Use archetype-appropriate background shapes
2953
+ if (archetype.name === "geometric-precision" || archetype.name === "op-art") // Rectangular shapes for geometric archetypes
2954
+ ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
2955
+ else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
1918
2956
  ctx.fill();
1919
2957
  }
1920
2958
  // Subtle concentric rings from center
1921
2959
  const ringCount = 2 + Math.floor(rng() * 3);
1922
2960
  ctx.globalAlpha = 0.02 + rng() * 0.03;
1923
- ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colors[0], 0.1);
2961
+ ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
1924
2962
  ctx.lineWidth = 1 * scaleFactor;
1925
2963
  for(let i = 1; i <= ringCount; i++){
1926
2964
  const r = Math.min(width, height) * 0.15 * i;
@@ -1934,7 +2972,6 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
1934
2972
  const symRoll = rng();
1935
2973
  const symmetryMode = symRoll < 0.10 ? "bilateral-x" : symRoll < 0.20 ? "bilateral-y" : symRoll < 0.25 ? "quad" : "none";
1936
2974
  // ── 3. Focal points + void zones ───────────────────────────────
1937
- // Rule-of-thirds intersection points for intentional composition
1938
2975
  const THIRDS_POINTS = [
1939
2976
  {
1940
2977
  x: 1 / 3,
@@ -1955,10 +2992,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
1955
2992
  ];
1956
2993
  const numFocal = 1 + Math.floor(rng() * 2);
1957
2994
  const focalPoints = [];
1958
- for(let f = 0; f < numFocal; f++)// 70% chance to snap to a rule-of-thirds point, 30% free placement
1959
- if (rng() < 0.7) {
2995
+ for(let f = 0; f < numFocal; f++)if (rng() < 0.7) {
1960
2996
  const tp = THIRDS_POINTS[Math.floor(rng() * THIRDS_POINTS.length)];
1961
- // Small jitter around the thirds point so it's not robotic
1962
2997
  focalPoints.push({
1963
2998
  x: width * (tp.x + (rng() - 0.5) * 0.08),
1964
2999
  y: height * (tp.y + (rng() - 0.5) * 0.08),
@@ -1969,7 +3004,6 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
1969
3004
  y: height * (0.2 + rng() * 0.6),
1970
3005
  strength: 0.3 + rng() * 0.4
1971
3006
  });
1972
- // Feature E: 1-2 void zones where shapes are sparse (negative space)
1973
3007
  const numVoids = Math.floor(rng() * 2) + 1;
1974
3008
  const voidZones = [];
1975
3009
  for(let v = 0; v < numVoids; v++)voidZones.push({
@@ -2001,20 +3035,24 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2001
3035
  }
2002
3036
  // Track all placed shapes for density checks and connecting curves
2003
3037
  const shapePositions = [];
3038
+ // Hero avoidance radius — shapes near the hero orient toward it
3039
+ let heroCenter = null;
2004
3040
  // ── 4b. Hero shape — a dominant focal element ───────────────────
2005
3041
  if (archetype.heroShape && rng() < 0.6) {
2006
3042
  const heroFocal = focalPoints[0];
3043
+ // Use shape palette hero candidates
2007
3044
  const heroPool = [
2008
- ...$4f72c5a314eddf25$var$SACRED_SHAPES,
2009
- "fibonacciSpiral",
2010
- "merkaba",
2011
- "fractal"
2012
- ];
2013
- const heroShape = heroPool.filter((s)=>shapeNames.includes(s))[Math.floor(rng() * heroPool.filter((s)=>shapeNames.includes(s)).length)] || shapeNames[Math.floor(rng() * shapeNames.length)];
3045
+ ...shapePalette.primary,
3046
+ ...shapePalette.supporting
3047
+ ].filter((s)=>(0, $e73976f898150d4d$export$4343b39fe47bd82c)[s]?.heroCandidate && shapeNames.includes(s));
3048
+ const heroShape = heroPool.length > 0 ? heroPool[Math.floor(rng() * heroPool.length)] : shapeNames[Math.floor(rng() * shapeNames.length)];
2014
3049
  const heroSize = adjustedMaxSize * (0.8 + rng() * 0.5);
2015
3050
  const heroRotation = rng() * 360;
2016
- const heroFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.05), 0.15 + rng() * 0.2);
2017
- const heroStroke = (0, $d016ad53434219a1$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.05);
3051
+ const heroFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.dominant, rng, 6, 0.05), bgLum), 0.15 + rng() * 0.2);
3052
+ const heroStroke = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.accent, rng, 6, 0.05), bgLum);
3053
+ // Get best style for this hero shape
3054
+ const heroProfile = (0, $e73976f898150d4d$export$4343b39fe47bd82c)[heroShape];
3055
+ const heroStyle = heroProfile ? heroProfile.bestStyles[Math.floor(rng() * heroProfile.bestStyles.length)] : rng() < 0.4 ? "watercolor" : "fill-and-stroke";
2018
3056
  ctx.globalAlpha = 0.5 + rng() * 0.2;
2019
3057
  (0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, heroShape, heroFocal.x, heroFocal.y, {
2020
3058
  fillColor: heroFill,
@@ -2025,14 +3063,20 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2025
3063
  proportionType: "GOLDEN_RATIO",
2026
3064
  glowRadius: (12 + rng() * 20) * scaleFactor,
2027
3065
  glowColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(heroStroke, 0.4),
2028
- gradientFillEnd: (0, $d016ad53434219a1$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1),
2029
- renderStyle: rng() < 0.4 ? "watercolor" : "fill-and-stroke",
3066
+ gradientFillEnd: (0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
3067
+ renderStyle: heroStyle,
2030
3068
  rng: rng
2031
3069
  });
2032
- shapePositions.push({
3070
+ heroCenter = {
2033
3071
  x: heroFocal.x,
2034
3072
  y: heroFocal.y,
2035
3073
  size: heroSize
3074
+ };
3075
+ shapePositions.push({
3076
+ x: heroFocal.x,
3077
+ y: heroFocal.y,
3078
+ size: heroSize,
3079
+ shape: heroShape
2036
3080
  });
2037
3081
  }
2038
3082
  // ── 5. Shape layers ────────────────────────────────────────────
@@ -2043,41 +3087,52 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2043
3087
  const numShapes = shapesPerLayer + Math.floor(rng() * shapesPerLayer * 0.3);
2044
3088
  const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);
2045
3089
  const layerSizeScale = 1 - layer * 0.15;
2046
- // Feature B: per-layer blend mode
3090
+ // Per-layer blend mode
2047
3091
  const layerBlend = (0, $c3de8257a8baa3b0$export$7bb7bff4e26fa06b)(rng);
2048
3092
  ctx.globalCompositeOperation = layerBlend;
2049
- // Feature C: per-layer render style bias — prefer archetype styles
3093
+ // Per-layer render style bias — prefer archetype styles
2050
3094
  const layerRenderStyle = rng() < 0.6 ? archetype.preferredStyles[Math.floor(rng() * archetype.preferredStyles.length)] : (0, $c3de8257a8baa3b0$export$9fd4e64b2acd410e)(rng);
2051
- // Feature D: atmospheric desaturation for later layers
2052
- const atmosphericDesat = layerRatio * 0.3; // 0 for first layer, up to 0.3 for last
3095
+ // Atmospheric desaturation for later layers
3096
+ const atmosphericDesat = layerRatio * 0.3;
2053
3097
  for(let i = 0; i < numShapes; i++){
2054
3098
  // Position from composition mode, then focal bias
2055
3099
  const rawPos = $4f72c5a314eddf25$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
2056
3100
  const [x, y] = applyFocalBias(rawPos.x, rawPos.y);
2057
- // Feature E: skip shapes in void zones, reduce in dense areas
3101
+ // Skip shapes in void zones, reduce in dense areas
2058
3102
  if ($4f72c5a314eddf25$var$isInVoidZone(x, y, voidZones)) {
2059
- // 85% chance to skip — allows a few shapes to bleed in
2060
3103
  if (rng() < 0.85) continue;
2061
3104
  }
2062
3105
  if ($4f72c5a314eddf25$var$localDensity(x, y, shapePositions, densityCheckRadius) > maxLocalDensity) {
2063
- if (rng() < 0.6) continue; // thin out dense areas
3106
+ if (rng() < 0.6) continue;
2064
3107
  }
2065
- // Weighted shape selection
2066
- const shape = $4f72c5a314eddf25$var$pickShape(rng, layerRatio, shapeNames);
2067
3108
  // Power distribution for size — archetype controls the curve
2068
3109
  const sizeT = Math.pow(rng(), archetype.sizePower);
2069
3110
  const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
3111
+ // Size fraction for affinity-aware shape selection
3112
+ const sizeFraction = size / adjustedMaxSize;
3113
+ // Palette-driven shape selection (replaces naive pickShape)
3114
+ const shape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, sizeFraction);
2070
3115
  // Flow-field rotation in flow-field mode, random otherwise
2071
- const rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
2072
- // Positional color blending + jitter
2073
- let fillBase = $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, colors, rng);
2074
- const strokeBase = colors[Math.floor(rng() * colors.length)];
2075
- // Feature D: desaturate colors on later layers for depth
3116
+ let rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
3117
+ // Hero avoidance: shapes near the hero orient toward it
3118
+ if (heroCenter) {
3119
+ const distToHero = Math.hypot(x - heroCenter.x, y - heroCenter.y);
3120
+ const heroInfluence = heroCenter.size * 1.5;
3121
+ if (distToHero < heroInfluence && distToHero > 0) {
3122
+ const angleToHero = Math.atan2(heroCenter.y - y, heroCenter.x - x) * 180 / Math.PI;
3123
+ const blendFactor = 1 - distToHero / heroInfluence;
3124
+ rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
3125
+ }
3126
+ }
3127
+ // Positional color from hierarchy + jitter
3128
+ let fillBase = $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, colorHierarchy, rng);
3129
+ const strokeBase = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
3130
+ // Desaturate colors on later layers for depth
2076
3131
  if (atmosphericDesat > 0) fillBase = (0, $d016ad53434219a1$export$fb75607d98509d9)(fillBase, atmosphericDesat);
2077
3132
  // Temperature contrast: shift foreground shapes opposite to background
2078
3133
  if (fgTempTarget) fillBase = (0, $d016ad53434219a1$export$51ea55f869b7e0d3)(fillBase, fgTempTarget, 0.15 + layerRatio * 0.1);
2079
- const fillColor = (0, $d016ad53434219a1$export$59539d800dbe6858)(fillBase, rng, 0.06);
2080
- const strokeColor = (0, $d016ad53434219a1$export$59539d800dbe6858)(strokeBase, rng, 0.05);
3134
+ const fillColor = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(fillBase, rng, 6, 0.05), bgLum);
3135
+ const strokeColor = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
2081
3136
  // Semi-transparent fill
2082
3137
  const fillAlpha = 0.2 + rng() * 0.5;
2083
3138
  const transparentFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, fillAlpha);
@@ -2091,12 +3146,16 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2091
3146
  const glowRadius = hasGlow ? (8 + rng() * 20) * scaleFactor : 0;
2092
3147
  // Gradient fill on ~30%
2093
3148
  const hasGradient = rng() < 0.3;
2094
- const gradientEnd = hasGradient ? (0, $d016ad53434219a1$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1) : undefined;
2095
- // Feature C: per-shape render style (70% use layer style, 30% pick their own)
2096
- const shapeRenderStyle = rng() < 0.7 ? layerRenderStyle : (0, $c3de8257a8baa3b0$export$9fd4e64b2acd410e)(rng);
2097
- // Feature F: organic edge jitter — applied via watercolor style on ~15% of shapes
3149
+ const gradientEnd = hasGradient ? (0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1) : undefined;
3150
+ // Affinity-aware render style selection
3151
+ const shapeRenderStyle = (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(shape, layerRenderStyle, rng);
3152
+ // Organic edge jitter — applied via watercolor style on ~15% of shapes
2098
3153
  const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
2099
3154
  const finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
3155
+ // Consistent light direction — subtle shadow offset
3156
+ const shadowDist = hasGlow ? 0 : size * 0.02;
3157
+ const shadowOffX = shadowDist * Math.cos(lightAngle);
3158
+ const shadowOffY = shadowDist * Math.sin(lightAngle);
2100
3159
  (0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
2101
3160
  fillColor: transparentFill,
2102
3161
  strokeColor: strokeColor,
@@ -2104,8 +3163,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2104
3163
  size: size,
2105
3164
  rotation: rotation,
2106
3165
  proportionType: "GOLDEN_RATIO",
2107
- glowRadius: glowRadius,
2108
- glowColor: hasGlow ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.6) : undefined,
3166
+ glowRadius: glowRadius || (shadowDist > 0 ? shadowDist * 2 : 0),
3167
+ glowColor: hasGlow ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
2109
3168
  gradientFillEnd: gradientEnd,
2110
3169
  renderStyle: finalRenderStyle,
2111
3170
  rng: rng
@@ -2113,18 +3172,51 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2113
3172
  shapePositions.push({
2114
3173
  x: x,
2115
3174
  y: y,
2116
- size: size
3175
+ size: size,
3176
+ shape: shape
2117
3177
  });
2118
- // ── 5b. Recursive nesting ──────────────────────────────────
3178
+ // ── 5b. Size echo — large shapes spawn trailing smaller copies ──
3179
+ if (size > adjustedMaxSize * 0.5 && rng() < 0.2) {
3180
+ const echoCount = 2 + Math.floor(rng() * 2);
3181
+ const echoAngle = rng() * Math.PI * 2;
3182
+ for(let e = 0; e < echoCount; e++){
3183
+ const echoScale = 0.3 - e * 0.08;
3184
+ const echoDist = size * (0.6 + e * 0.4);
3185
+ const echoX = x + Math.cos(echoAngle) * echoDist;
3186
+ const echoY = y + Math.sin(echoAngle) * echoDist;
3187
+ const echoSize = size * Math.max(0.1, echoScale);
3188
+ if (echoX < 0 || echoX > width || echoY < 0 || echoY > height) continue;
3189
+ ctx.globalAlpha = layerOpacity * (0.4 - e * 0.1);
3190
+ (0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, echoX, echoY, {
3191
+ fillColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, fillAlpha * 0.6),
3192
+ strokeColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(strokeColor, 0.4),
3193
+ strokeWidth: strokeWidth * 0.6,
3194
+ size: echoSize,
3195
+ rotation: rotation + (e + 1) * 15,
3196
+ proportionType: "GOLDEN_RATIO",
3197
+ renderStyle: finalRenderStyle,
3198
+ rng: rng
3199
+ });
3200
+ shapePositions.push({
3201
+ x: echoX,
3202
+ y: echoY,
3203
+ size: echoSize,
3204
+ shape: shape
3205
+ });
3206
+ }
3207
+ }
3208
+ // ── 5c. Recursive nesting ──────────────────────────────────
2119
3209
  if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
2120
3210
  const innerCount = 1 + Math.floor(rng() * 3);
2121
3211
  for(let n = 0; n < innerCount; n++){
2122
- const innerShape = $4f72c5a314eddf25$var$pickShape(rng, Math.min(1, layerRatio + 0.3), shapeNames);
3212
+ // Pick inner shape from palette affinities
3213
+ const innerSizeFraction = size * 0.25 / adjustedMaxSize;
3214
+ const innerShape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, innerSizeFraction);
2123
3215
  const innerSize = size * (0.15 + rng() * 0.25);
2124
3216
  const innerOffX = (rng() - 0.5) * size * 0.4;
2125
3217
  const innerOffY = (rng() - 0.5) * size * 0.4;
2126
3218
  const innerRot = rng() * 360;
2127
- const innerFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1), 0.3 + rng() * 0.4);
3219
+ const innerFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1), 0.3 + rng() * 0.4);
2128
3220
  ctx.globalAlpha = layerOpacity * 0.7;
2129
3221
  (0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, innerShape, x + innerOffX, y + innerOffY, {
2130
3222
  fillColor: innerFill,
@@ -2133,7 +3225,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2133
3225
  size: innerSize,
2134
3226
  rotation: innerRot,
2135
3227
  proportionType: "GOLDEN_RATIO",
2136
- renderStyle: shapeRenderStyle,
3228
+ renderStyle: (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(innerShape, layerRenderStyle, rng),
2137
3229
  rng: rng
2138
3230
  });
2139
3231
  }
@@ -2142,7 +3234,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2142
3234
  }
2143
3235
  // Reset blend mode for post-processing passes
2144
3236
  ctx.globalCompositeOperation = "source-over";
2145
- // ── 6. Flow-line pass (Feature H: tapered brush strokes) ───────
3237
+ // ── 6. Flow-line pass variable color, branching, pressure ────
2146
3238
  const baseFlowLines = 6 + Math.floor(rng() * 10);
2147
3239
  const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
2148
3240
  for(let i = 0; i < numFlowLines; i++){
@@ -2151,9 +3243,13 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2151
3243
  const steps = 30 + Math.floor(rng() * 40);
2152
3244
  const stepLen = (3 + rng() * 5) * scaleFactor;
2153
3245
  const startWidth = (1 + rng() * 3) * scaleFactor;
2154
- const lineColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.4);
3246
+ // Variable color: interpolate between two hierarchy colors along the stroke
3247
+ const lineColorStart = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
3248
+ const lineColorEnd = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
2155
3249
  const lineAlpha = 0.06 + rng() * 0.1;
2156
- // Draw as individual segments with tapering width
3250
+ // Pressure simulation: sinusoidal width variation
3251
+ const pressureFreq = 2 + rng() * 4;
3252
+ const pressurePhase = rng() * Math.PI * 2;
2157
3253
  let prevX = fx;
2158
3254
  let prevY = fy;
2159
3255
  for(let s = 0; s < steps; s++){
@@ -2161,37 +3257,61 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2161
3257
  fx += Math.cos(angle) * stepLen;
2162
3258
  fy += Math.sin(angle) * stepLen;
2163
3259
  if (fx < 0 || fx > width || fy < 0 || fy > height) break;
2164
- // Taper: thick at start, thin at end
2165
- const taper = 1 - s / steps * 0.8;
3260
+ const t = s / steps;
3261
+ // Taper + pressure
3262
+ const taper = 1 - t * 0.8;
3263
+ const pressure = 0.6 + 0.4 * Math.sin(t * pressureFreq * Math.PI + pressurePhase);
2166
3264
  ctx.globalAlpha = lineAlpha * taper;
3265
+ // Interpolate color along stroke
3266
+ const lineColor = t < 0.5 ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(lineColorStart, 0.4 + t * 0.2) : (0, $d016ad53434219a1$export$f2121afcad3d553f)(lineColorEnd, 0.4 + (1 - t) * 0.2);
2167
3267
  ctx.strokeStyle = lineColor;
2168
- ctx.lineWidth = startWidth * taper;
3268
+ ctx.lineWidth = startWidth * taper * pressure;
2169
3269
  ctx.lineCap = "round";
2170
3270
  ctx.beginPath();
2171
3271
  ctx.moveTo(prevX, prevY);
2172
3272
  ctx.lineTo(fx, fy);
2173
3273
  ctx.stroke();
3274
+ // Branching: ~12% chance per step to spawn a thinner child stroke
3275
+ if (rng() < 0.12 && s > 5 && s < steps - 10) {
3276
+ const branchAngle = angle + (rng() < 0.5 ? 1 : -1) * (0.3 + rng() * 0.5);
3277
+ let bx = fx;
3278
+ let by = fy;
3279
+ let bPrevX = fx;
3280
+ let bPrevY = fy;
3281
+ const branchSteps = 5 + Math.floor(rng() * 10);
3282
+ const branchWidth = startWidth * taper * 0.4;
3283
+ for(let bs = 0; bs < branchSteps; bs++){
3284
+ const bAngle = branchAngle + (rng() - 0.5) * 0.2;
3285
+ bx += Math.cos(bAngle) * stepLen * 0.8;
3286
+ by += Math.sin(bAngle) * stepLen * 0.8;
3287
+ if (bx < 0 || bx > width || by < 0 || by > height) break;
3288
+ const bTaper = 1 - bs / branchSteps * 0.9;
3289
+ ctx.globalAlpha = lineAlpha * taper * bTaper * 0.6;
3290
+ ctx.lineWidth = branchWidth * bTaper;
3291
+ ctx.beginPath();
3292
+ ctx.moveTo(bPrevX, bPrevY);
3293
+ ctx.lineTo(bx, by);
3294
+ ctx.stroke();
3295
+ bPrevX = bx;
3296
+ bPrevY = by;
3297
+ }
3298
+ }
2174
3299
  prevX = fx;
2175
3300
  prevY = fy;
2176
3301
  }
2177
3302
  }
2178
3303
  // ── 6b. Apply symmetry mirroring ─────────────────────────────────
2179
- // Mirror the rendered content (shapes + flow lines) before post-processing.
2180
- // Uses ctx.canvas which is available in both Node (@napi-rs/canvas) and browsers.
2181
3304
  if (symmetryMode !== "none") {
2182
3305
  const canvas = ctx.canvas;
2183
3306
  ctx.save();
2184
3307
  if (symmetryMode === "bilateral-x" || symmetryMode === "quad") {
2185
- // Mirror left half onto right half
2186
3308
  ctx.save();
2187
3309
  ctx.translate(width, 0);
2188
3310
  ctx.scale(-1, 1);
2189
- // Draw the left half (0 to cx) onto the mirrored right side
2190
3311
  ctx.drawImage(canvas, 0, 0, Math.ceil(cx), height, 0, 0, Math.ceil(cx), height);
2191
3312
  ctx.restore();
2192
3313
  }
2193
3314
  if (symmetryMode === "bilateral-y" || symmetryMode === "quad") {
2194
- // Mirror top half onto bottom half
2195
3315
  ctx.save();
2196
3316
  ctx.translate(0, height);
2197
3317
  ctx.scale(1, -1);
@@ -2214,7 +3334,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2214
3334
  }
2215
3335
  // ── 8. Vignette — darken edges to draw the eye inward ───────────
2216
3336
  ctx.globalAlpha = 1;
2217
- const vignetteStrength = 0.25 + rng() * 0.2; // 25-45% edge darkening
3337
+ const vignetteStrength = 0.25 + rng() * 0.2;
2218
3338
  const vigGrad = ctx.createRadialGradient(cx, cy, Math.min(width, height) * 0.3, cx, cy, bgRadius);
2219
3339
  vigGrad.addColorStop(0, "rgba(0,0,0,0)");
2220
3340
  vigGrad.addColorStop(0.6, "rgba(0,0,0,0)");
@@ -2240,13 +3360,57 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
2240
3360
  const cpx = mx + -dy / (dist || 1) * bulge;
2241
3361
  const cpy = my + dx / (dist || 1) * bulge;
2242
3362
  ctx.globalAlpha = 0.06 + rng() * 0.1;
2243
- ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.3);
3363
+ ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
2244
3364
  ctx.beginPath();
2245
3365
  ctx.moveTo(a.x, a.y);
2246
3366
  ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);
2247
3367
  ctx.stroke();
2248
3368
  }
2249
3369
  }
3370
+ // ── 10. Post-processing ────────────────────────────────────────
3371
+ // 10a. Color grading — unified tone across the whole image
3372
+ // Apply as a semi-transparent overlay in the grade hue
3373
+ ctx.globalAlpha = colorGrade.intensity * 0.25;
3374
+ ctx.globalCompositeOperation = "soft-light";
3375
+ const gradeHsl = `hsl(${Math.round(colorGrade.hue)}, 40%, 50%)`;
3376
+ ctx.fillStyle = gradeHsl;
3377
+ ctx.fillRect(0, 0, width, height);
3378
+ ctx.globalCompositeOperation = "source-over";
3379
+ // 10b. Chromatic aberration — subtle RGB channel offset at edges
3380
+ // Only apply for neon/cosmic/ethereal archetypes where it fits
3381
+ const chromaArchetypes = [
3382
+ "neon-glow",
3383
+ "cosmic",
3384
+ "ethereal"
3385
+ ];
3386
+ if (chromaArchetypes.includes(archetype.name)) {
3387
+ const chromaOffset = Math.ceil(2 * scaleFactor);
3388
+ const canvas = ctx.canvas;
3389
+ // Shift red channel slightly
3390
+ ctx.globalAlpha = 0.03;
3391
+ ctx.globalCompositeOperation = "screen";
3392
+ ctx.drawImage(canvas, chromaOffset, 0, width, height, 0, 0, width, height);
3393
+ // Shift blue channel opposite
3394
+ ctx.drawImage(canvas, -chromaOffset, 0, width, height, 0, 0, width, height);
3395
+ ctx.globalCompositeOperation = "source-over";
3396
+ }
3397
+ // 10c. Bloom — soft glow on bright areas for neon/cosmic archetypes
3398
+ const bloomArchetypes = [
3399
+ "neon-glow",
3400
+ "cosmic"
3401
+ ];
3402
+ if (bloomArchetypes.includes(archetype.name)) {
3403
+ const canvas = ctx.canvas;
3404
+ ctx.globalAlpha = 0.08;
3405
+ ctx.globalCompositeOperation = "screen";
3406
+ // Draw the image slightly scaled up and blurred via shadow
3407
+ ctx.save();
3408
+ ctx.shadowBlur = 30 * scaleFactor;
3409
+ ctx.shadowColor = "rgba(255,255,255,0.3)";
3410
+ ctx.drawImage(canvas, 0, 0, width, height);
3411
+ ctx.restore();
3412
+ ctx.globalCompositeOperation = "source-over";
3413
+ }
2250
3414
  ctx.globalAlpha = 1;
2251
3415
  }
2252
3416