git-hash-art 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ALGORITHM.md CHANGED
@@ -11,12 +11,13 @@ Hash String
11
11
 
12
12
  ├─► Archetype Selection (1 of 10 visual personalities)
13
13
 
14
- ├─► Color Scheme (palette mode from archetype + temperature mode)
14
+ ├─► Color Scheme (palette mode from archetype + temperature mode + contrast enforcement)
15
15
 
16
16
  └─► Rendering Pipeline (parameters overridden by archetype)
17
17
 
18
18
  0. Archetype Override (gridSize, layers, opacity, sizes, styles)
19
19
  1. Background Layer (7 styles: radial, linear, solid, multi-stop)
20
+ 1a. Background Luminance → contrast enforcement threshold
20
21
  1b. Layered Background (faint shapes + concentric rings)
21
22
  2. Composition Mode Selection
22
23
  2b. Symmetry Mode Selection (none / bilateral / quad)
@@ -27,7 +28,8 @@ Hash String
27
28
  │ ├─ Blend Mode (per-layer compositing)
28
29
  │ ├─ Render Style (archetype-preferred + random mix)
29
30
  │ ├─ Position (composition mode + focal bias + density check)
30
- │ ├─ Shape Selection (layer-weighted)
31
+ │ ├─ Shape Selection (4 categories: basic, complex, sacred, procedural)
32
+ │ ├─ Contrast Enforcement (ensure readability vs background)
31
33
  │ ├─ Atmospheric Depth (desaturation on later layers)
32
34
  │ ├─ Temperature Contrast (foreground opposite to background)
33
35
  │ ├─ Styling (transparency, glow, gradients, color jitter)
@@ -267,13 +269,25 @@ Later layers progressively desaturate their colors (0% on layer 0, up to 30% on
267
269
 
268
270
  ### Shape Selection (Layer-Weighted)
269
271
 
270
- Shapes are divided into three categories with weights that shift across layers:
272
+ Shapes are divided into four categories with weights that shift across layers:
271
273
 
272
274
  | Category | Shapes | Early layers | Late layers |
273
275
  |----------|--------|-------------|-------------|
274
276
  | **Basic** | circle, square, triangle, hexagon, diamond, cube | High weight | Low weight |
275
277
  | **Complex** | star, platonic solid, fibonacci spiral, islamic pattern, celtic knot, merkaba, fractal | Medium | Medium-high |
276
278
  | **Sacred** | mandala, flower of life, tree of life, Metatron's cube, Sri Yantra, seed of life, vesica piscis, torus, egg of life | Low | High |
279
+ | **Procedural** | blob, ngon, lissajous, superellipse, spirograph, waveRing, rose | Medium (always present) | Medium-high |
280
+
281
+ Procedural shapes are hash-derived — their geometry is generated from the RNG, so every hash produces unique shapes that don't exist in any other generation. See the Procedural Shapes section below for details.
282
+
283
+ ### Contrast Enforcement
284
+
285
+ After color selection, every foreground color (fills, strokes, flow lines, connecting curves) is checked against the average background luminance. If the luminance difference is below the minimum threshold (0.15), the color is adjusted:
286
+
287
+ - **Light backgrounds** — foreground colors are darkened and saturated
288
+ - **Dark backgrounds** — foreground colors are lightened and saturated
289
+
290
+ This prevents the white-on-white and dark-on-dark readability problems that occur when light palette modes (pastel-light, high-contrast) combine with light background styles (solid-light, radial-light).
277
291
 
278
292
  ### Size Distribution
279
293
 
@@ -366,6 +380,19 @@ Mathematically precise sacred geometry patterns:
366
380
  - **Torus** — 2D projection of a torus via line segments
367
381
  - **Egg of Life** — 7 circles in tight hexagonal packing
368
382
 
383
+ ### Procedural Shapes
384
+ Hash-derived shapes whose geometry is generated from the RNG. Every hash produces unique shapes that don't exist in any other generation:
385
+
386
+ | Shape | Algorithm | Hash Controls |
387
+ |-------|-----------|---------------|
388
+ | **Blob** | Smooth closed curve via quadratic bezier through 5-9 control points arranged around a circle | Number of lobes (5-9), radius jitter per lobe (50-100%) |
389
+ | **Ngon** | Irregular polygon with independent vertex displacement | Side count (3-12), vertex jitter amount (10-50%) |
390
+ | **Lissajous** | Parametric curve `x = sin(a*t + φ), y = sin(b*t)` | Frequency ratios a,b (1-5 each), phase offset φ |
391
+ | **Superellipse** | `|x|^n + |y|^n = 1` rendered parametrically | Exponent n: 0.3 (spiky astroid) → 2 (circle) → 5 (rounded rectangle) |
392
+ | **Spirograph** | Hypotrochoid curve `(R-r)cos(t) + d*cos((R-r)t/r)` | Inner radius ratio r (0.2-0.8), pen distance d (0.3-1.0) |
393
+ | **Wave Ring** | Concentric rings with sinusoidal radial displacement | Ring count (2-5), wave frequency (3-14), amplitude (5-20%) |
394
+ | **Rose** | Polar rose curve `r = cos(k*θ)` | Petal parameter k (2-7), producing k or 2k petals |
395
+
369
396
  ## Configuration
370
397
 
371
398
  All parameters are exposed via `GenerationConfig`:
package/CHANGELOG.md CHANGED
@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [0.7.0](https://github.com/gfargo/git-hash-art/compare/0.6.0...0.7.0)
8
+
9
+ - feat: procedural shape generators and contrast enforcement [`#13`](https://github.com/gfargo/git-hash-art/pull/13)
10
+ - feat: procedural shapes and contrast enforcement [`4f018ab`](https://github.com/gfargo/git-hash-art/commit/4f018abb4c89849954ccc941ee7e9ab468231f78)
11
+ - docs: update ALGORITHM.md with procedural shapes and contrast enforcement [`3e044fd`](https://github.com/gfargo/git-hash-art/commit/3e044fd940fbc0f62e3a9696fa815145cf315f67)
12
+
7
13
  #### [0.6.0](https://github.com/gfargo/git-hash-art/compare/0.5.0...0.6.0)
8
14
 
15
+ > 19 March 2026
16
+
9
17
  - feat: archetype system, palette modes, background variety, and CLI [`#12`](https://github.com/gfargo/git-hash-art/pull/12)
10
18
  - feat: archetype system for dramatically different visual personalities [`2a1b919`](https://github.com/gfargo/git-hash-art/commit/2a1b919c51d3bfbd9d5c7a381ea5e104f81df2f6)
11
19
  - feat: add CLI for generating art from terminal [`184372a`](https://github.com/gfargo/git-hash-art/commit/184372a584f68031bf44379f385f2e78691f98aa)
package/dist/browser.js CHANGED
@@ -403,6 +403,30 @@ function $b5a262d09b87e373$export$51ea55f869b7e0d3(hex, target, amount) {
403
403
  const [h, s, l] = $b5a262d09b87e373$var$hexToHsl(hex);
404
404
  return $b5a262d09b87e373$var$hslToHex($b5a262d09b87e373$var$shiftHueToward(h, target, amount), s, l);
405
405
  }
406
+ function $b5a262d09b87e373$export$5c6e3c2b59b7fbbe(hex) {
407
+ const [r, g, b] = $b5a262d09b87e373$var$hexToRgb(hex).map((c)=>{
408
+ const s = c / 255;
409
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
410
+ });
411
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
412
+ }
413
+ function $b5a262d09b87e373$export$90ad0e6170cf6af5(fgHex, bgLuminance, minContrast = 0.15) {
414
+ const fgLum = $b5a262d09b87e373$export$5c6e3c2b59b7fbbe(fgHex);
415
+ const diff = Math.abs(fgLum - bgLuminance);
416
+ if (diff >= minContrast) return fgHex;
417
+ const [h, s, l] = $b5a262d09b87e373$var$hexToHsl(fgHex);
418
+ if (bgLuminance > 0.5) {
419
+ // Light background — darken and boost saturation
420
+ const targetL = Math.max(0.08, l - (minContrast - diff) * 1.5);
421
+ const targetS = Math.min(1, s + 0.2);
422
+ return $b5a262d09b87e373$var$hslToHex(h, targetS, targetL);
423
+ } else {
424
+ // Dark background — lighten and boost saturation
425
+ const targetL = Math.min(0.92, l + (minContrast - diff) * 1.5);
426
+ const targetS = Math.min(1, s + 0.15);
427
+ return $b5a262d09b87e373$var$hslToHex(h, targetS, targetL);
428
+ }
429
+ }
406
430
 
407
431
 
408
432
 
@@ -1183,10 +1207,172 @@ const $77711f013715e6da$export$c2fc138f94dd4b2a = {
1183
1207
  };
1184
1208
 
1185
1209
 
1210
+ /**
1211
+ * Procedural shape generators — hash-derived shapes that are unique
1212
+ * per generation. Unlike the fixed shape library, these produce geometry
1213
+ * that doesn't repeat across hashes.
1214
+ *
1215
+ * All draw functions accept an RNG via the config parameter so the
1216
+ * shapes are deterministic from the hash.
1217
+ */ const $2899cc29bfcdb86b$export$580f80cfb9de73bc = (ctx, size, config)=>{
1218
+ const rng = config?.rng ?? Math.random;
1219
+ const r = size / 2;
1220
+ const numPoints = 5 + Math.floor(rng() * 5); // 5-9 lobes
1221
+ const points = [];
1222
+ for(let i = 0; i < numPoints; i++){
1223
+ const angle = i / numPoints * Math.PI * 2;
1224
+ const jitter = 0.5 + rng() * 0.5; // radius varies 50-100%
1225
+ points.push({
1226
+ x: Math.cos(angle) * r * jitter,
1227
+ y: Math.sin(angle) * r * jitter
1228
+ });
1229
+ }
1230
+ ctx.beginPath();
1231
+ // Start at midpoint between last and first point
1232
+ const last = points[points.length - 1];
1233
+ const first = points[0];
1234
+ ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
1235
+ for(let i = 0; i < numPoints; i++){
1236
+ const curr = points[i];
1237
+ const next = points[(i + 1) % numPoints];
1238
+ const midX = (curr.x + next.x) / 2;
1239
+ const midY = (curr.y + next.y) / 2;
1240
+ ctx.quadraticCurveTo(curr.x, curr.y, midX, midY);
1241
+ }
1242
+ ctx.closePath();
1243
+ };
1244
+ const $2899cc29bfcdb86b$export$7a6094023f0902a6 = (ctx, size, config)=>{
1245
+ const rng = config?.rng ?? Math.random;
1246
+ const r = size / 2;
1247
+ const sides = 3 + Math.floor(rng() * 10); // 3-12 sides
1248
+ const jitterAmount = 0.1 + rng() * 0.4; // 10-50% vertex displacement
1249
+ ctx.beginPath();
1250
+ for(let i = 0; i < sides; i++){
1251
+ const angle = i / sides * Math.PI * 2 - Math.PI / 2;
1252
+ const radiusJitter = 1 - jitterAmount + rng() * jitterAmount * 2;
1253
+ const x = Math.cos(angle) * r * radiusJitter;
1254
+ const y = Math.sin(angle) * r * radiusJitter;
1255
+ if (i === 0) ctx.moveTo(x, y);
1256
+ else ctx.lineTo(x, y);
1257
+ }
1258
+ ctx.closePath();
1259
+ };
1260
+ const $2899cc29bfcdb86b$export$ef56b4a8316e47d5 = (ctx, size, config)=>{
1261
+ const rng = config?.rng ?? Math.random;
1262
+ const r = size / 2;
1263
+ // Frequency ratios — small integers produce recognizable patterns
1264
+ const freqA = 1 + Math.floor(rng() * 5); // 1-5
1265
+ const freqB = 1 + Math.floor(rng() * 5); // 1-5
1266
+ const phase = rng() * Math.PI; // phase offset
1267
+ const steps = 120;
1268
+ ctx.beginPath();
1269
+ for(let i = 0; i <= steps; i++){
1270
+ const t = i / steps * Math.PI * 2;
1271
+ const x = Math.sin(freqA * t + phase) * r;
1272
+ const y = Math.sin(freqB * t) * r;
1273
+ if (i === 0) ctx.moveTo(x, y);
1274
+ else ctx.lineTo(x, y);
1275
+ }
1276
+ ctx.closePath();
1277
+ };
1278
+ const $2899cc29bfcdb86b$export$1db9219b4f34658c = (ctx, size, config)=>{
1279
+ const rng = config?.rng ?? Math.random;
1280
+ const r = size / 2;
1281
+ // Exponent range: 0.3 (spiky astroid) to 5 (rounded rectangle)
1282
+ const n = 0.3 + rng() * 4.7;
1283
+ const steps = 120;
1284
+ ctx.beginPath();
1285
+ for(let i = 0; i <= steps; i++){
1286
+ const t = i / steps * Math.PI * 2;
1287
+ const cosT = Math.cos(t);
1288
+ const sinT = Math.sin(t);
1289
+ // Superellipse parametric form
1290
+ const x = Math.sign(cosT) * Math.pow(Math.abs(cosT), 2 / n) * r;
1291
+ const y = Math.sign(sinT) * Math.pow(Math.abs(sinT), 2 / n) * r;
1292
+ if (i === 0) ctx.moveTo(x, y);
1293
+ else ctx.lineTo(x, y);
1294
+ }
1295
+ ctx.closePath();
1296
+ };
1297
+ const $2899cc29bfcdb86b$export$b027c64d22b01985 = (ctx, size, config)=>{
1298
+ const rng = config?.rng ?? Math.random;
1299
+ const scale = size / 2;
1300
+ // R = outer radius, r = inner radius, d = pen distance from inner center
1301
+ const R = 1;
1302
+ const r = 0.2 + rng() * 0.6; // 0.2-0.8
1303
+ const d = 0.3 + rng() * 0.7; // 0.3-1.0
1304
+ // Number of full rotations needed to close the curve
1305
+ const gcd = (a, b)=>{
1306
+ const ai = Math.round(a * 1000);
1307
+ const bi = Math.round(b * 1000);
1308
+ const g = (x, y)=>y === 0 ? x : g(y, x % y);
1309
+ return g(ai, bi) / 1000;
1310
+ };
1311
+ const period = r / gcd(R, r);
1312
+ const maxT = Math.min(period, 10) * Math.PI * 2; // cap at 10 rotations
1313
+ const steps = Math.min(600, Math.floor(maxT * 20));
1314
+ ctx.beginPath();
1315
+ for(let i = 0; i <= steps; i++){
1316
+ const t = i / steps * maxT;
1317
+ const x = ((R - r) * Math.cos(t) + d * Math.cos((R - r) / r * t)) * scale / (1 + d);
1318
+ const y = ((R - r) * Math.sin(t) - d * Math.sin((R - r) / r * t)) * scale / (1 + d);
1319
+ if (i === 0) ctx.moveTo(x, y);
1320
+ else ctx.lineTo(x, y);
1321
+ }
1322
+ ctx.closePath();
1323
+ };
1324
+ const $2899cc29bfcdb86b$export$7608ccd03bfb705d = (ctx, size, config)=>{
1325
+ const rng = config?.rng ?? Math.random;
1326
+ const r = size / 2;
1327
+ const rings = 2 + Math.floor(rng() * 4); // 2-5 rings
1328
+ const freq = 3 + Math.floor(rng() * 12); // 3-14 waves per ring
1329
+ const amp = 0.05 + rng() * 0.15; // 5-20% of radius
1330
+ ctx.beginPath();
1331
+ for(let ring = 0; ring < rings; ring++){
1332
+ const baseR = r * (0.3 + ring / rings * 0.7);
1333
+ const steps = 80;
1334
+ for(let i = 0; i <= steps; i++){
1335
+ const t = i / steps * Math.PI * 2;
1336
+ const wave = Math.sin(t * freq + ring * 1.5) * baseR * amp;
1337
+ const x = Math.cos(t) * (baseR + wave);
1338
+ const y = Math.sin(t) * (baseR + wave);
1339
+ if (i === 0) ctx.moveTo(x, y);
1340
+ else ctx.lineTo(x, y);
1341
+ }
1342
+ }
1343
+ };
1344
+ const $2899cc29bfcdb86b$export$11a377e7498bb523 = (ctx, size, config)=>{
1345
+ const rng = config?.rng ?? Math.random;
1346
+ const r = size / 2;
1347
+ const k = 2 + Math.floor(rng() * 6); // 2-7 petal parameter
1348
+ const steps = 200;
1349
+ ctx.beginPath();
1350
+ for(let i = 0; i <= steps; i++){
1351
+ const theta = i / steps * Math.PI * 2 * (k % 2 === 0 ? 1 : 2);
1352
+ const rr = Math.cos(k * theta) * r;
1353
+ const x = rr * Math.cos(theta);
1354
+ const y = rr * Math.sin(theta);
1355
+ if (i === 0) ctx.moveTo(x, y);
1356
+ else ctx.lineTo(x, y);
1357
+ }
1358
+ ctx.closePath();
1359
+ };
1360
+ const $2899cc29bfcdb86b$export$40cfb4c637f2fbb5 = {
1361
+ blob: $2899cc29bfcdb86b$export$580f80cfb9de73bc,
1362
+ ngon: $2899cc29bfcdb86b$export$7a6094023f0902a6,
1363
+ lissajous: $2899cc29bfcdb86b$export$ef56b4a8316e47d5,
1364
+ superellipse: $2899cc29bfcdb86b$export$1db9219b4f34658c,
1365
+ spirograph: $2899cc29bfcdb86b$export$b027c64d22b01985,
1366
+ waveRing: $2899cc29bfcdb86b$export$7608ccd03bfb705d,
1367
+ rose: $2899cc29bfcdb86b$export$11a377e7498bb523
1368
+ };
1369
+
1370
+
1186
1371
  const $e41b41d8dcf837ad$export$4ff7fc6f1af248b5 = {
1187
1372
  ...(0, $44f5b87a40c9680b$export$492753207a5258e1),
1188
1373
  ...(0, $f0f1a7293548e501$export$dbe318a13ce51887),
1189
- ...(0, $77711f013715e6da$export$c2fc138f94dd4b2a)
1374
+ ...(0, $77711f013715e6da$export$c2fc138f94dd4b2a),
1375
+ ...(0, $2899cc29bfcdb86b$export$40cfb4c637f2fbb5)
1190
1376
  };
1191
1377
 
1192
1378
 
@@ -1390,7 +1576,9 @@ function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
1390
1576
  ctx.lineWidth = strokeWidth;
1391
1577
  const drawFunction = (0, $e41b41d8dcf837ad$export$4ff7fc6f1af248b5)[shape];
1392
1578
  if (drawFunction) {
1393
- drawFunction(ctx, size);
1579
+ drawFunction(ctx, size, {
1580
+ rng: rng
1581
+ });
1394
1582
  $e0f99502ff383dd8$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
1395
1583
  }
1396
1584
  // Reset shadow so patterns aren't double-glowed
@@ -1679,6 +1867,15 @@ const $1f63dc64b5593c73$var$SACRED_SHAPES = [
1679
1867
  "torus",
1680
1868
  "eggOfLife"
1681
1869
  ];
1870
+ const $1f63dc64b5593c73$var$PROCEDURAL_SHAPES = [
1871
+ "blob",
1872
+ "ngon",
1873
+ "lissajous",
1874
+ "superellipse",
1875
+ "spirograph",
1876
+ "waveRing",
1877
+ "rose"
1878
+ ];
1682
1879
  const $1f63dc64b5593c73$var$COMPOSITION_MODES = [
1683
1880
  "radial",
1684
1881
  "flow-field",
@@ -1691,12 +1888,14 @@ function $1f63dc64b5593c73$var$pickShape(rng, layerRatio, shapeNames) {
1691
1888
  const basicW = 1 - layerRatio * 0.6;
1692
1889
  const complexW = 0.3 + layerRatio * 0.3;
1693
1890
  const sacredW = 0.1 + layerRatio * 0.4;
1694
- const total = basicW + complexW + sacredW;
1891
+ const proceduralW = 0.25 + layerRatio * 0.2; // always present, grows with depth
1892
+ const total = basicW + complexW + sacredW + proceduralW;
1695
1893
  const roll = rng() * total;
1696
1894
  let pool;
1697
1895
  if (roll < basicW) pool = $1f63dc64b5593c73$var$BASIC_SHAPES;
1698
1896
  else if (roll < basicW + complexW) pool = $1f63dc64b5593c73$var$COMPLEX_SHAPES;
1699
- else pool = $1f63dc64b5593c73$var$SACRED_SHAPES;
1897
+ else if (roll < basicW + complexW + sacredW) pool = $1f63dc64b5593c73$var$SACRED_SHAPES;
1898
+ else pool = $1f63dc64b5593c73$var$PROCEDURAL_SHAPES;
1700
1899
  const available = pool.filter((s)=>shapeNames.includes(s));
1701
1900
  if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
1702
1901
  return available[Math.floor(rng() * available.length)];
@@ -1879,6 +2078,8 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
1879
2078
  // ── 1. Background ──────────────────────────────────────────────
1880
2079
  const bgRadius = Math.hypot(cx, cy);
1881
2080
  $1f63dc64b5593c73$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
2081
+ // Compute average background luminance for contrast enforcement
2082
+ const bgLum = ((0, $b5a262d09b87e373$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $b5a262d09b87e373$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
1882
2083
  // ── 1b. Layered background (Feature G) ─────────────────────────
1883
2084
  // Draw large, very faint shapes to give the background texture
1884
2085
  const bgShapeCount = 3 + Math.floor(rng() * 4);
@@ -1990,8 +2191,8 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
1990
2191
  const heroShape = heroPool.filter((s)=>shapeNames.includes(s))[Math.floor(rng() * heroPool.filter((s)=>shapeNames.includes(s)).length)] || shapeNames[Math.floor(rng() * shapeNames.length)];
1991
2192
  const heroSize = adjustedMaxSize * (0.8 + rng() * 0.5);
1992
2193
  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);
2194
+ const heroFill = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$90ad0e6170cf6af5)((0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.05), bgLum), 0.15 + rng() * 0.2);
2195
+ const heroStroke = (0, $b5a262d09b87e373$export$90ad0e6170cf6af5)((0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.05), bgLum);
1995
2196
  ctx.globalAlpha = 0.5 + rng() * 0.2;
1996
2197
  (0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, heroShape, heroFocal.x, heroFocal.y, {
1997
2198
  fillColor: heroFill,
@@ -2053,8 +2254,8 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
2053
2254
  if (atmosphericDesat > 0) fillBase = (0, $b5a262d09b87e373$export$fb75607d98509d9)(fillBase, atmosphericDesat);
2054
2255
  // Temperature contrast: shift foreground shapes opposite to background
2055
2256
  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);
2257
+ const fillColor = (0, $b5a262d09b87e373$export$90ad0e6170cf6af5)((0, $b5a262d09b87e373$export$59539d800dbe6858)(fillBase, rng, 0.06), bgLum);
2258
+ const strokeColor = (0, $b5a262d09b87e373$export$90ad0e6170cf6af5)((0, $b5a262d09b87e373$export$59539d800dbe6858)(strokeBase, rng, 0.05), bgLum);
2058
2259
  // Semi-transparent fill
2059
2260
  const fillAlpha = 0.2 + rng() * 0.5;
2060
2261
  const transparentFill = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(fillColor, fillAlpha);
@@ -2128,7 +2329,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
2128
2329
  const steps = 30 + Math.floor(rng() * 40);
2129
2330
  const stepLen = (3 + rng() * 5) * scaleFactor;
2130
2331
  const startWidth = (1 + rng() * 3) * scaleFactor;
2131
- const lineColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.4);
2332
+ const lineColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$90ad0e6170cf6af5)(colors[Math.floor(rng() * colors.length)], bgLum), 0.4);
2132
2333
  const lineAlpha = 0.06 + rng() * 0.1;
2133
2334
  // Draw as individual segments with tapering width
2134
2335
  let prevX = fx;
@@ -2217,7 +2418,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
2217
2418
  const cpx = mx + -dy / (dist || 1) * bulge;
2218
2419
  const cpy = my + dx / (dist || 1) * bulge;
2219
2420
  ctx.globalAlpha = 0.06 + rng() * 0.1;
2220
- ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.3);
2421
+ ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$90ad0e6170cf6af5)(colors[Math.floor(rng() * colors.length)], bgLum), 0.3);
2221
2422
  ctx.beginPath();
2222
2423
  ctx.moveTo(a.x, a.y);
2223
2424
  ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);