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