git-hash-art 0.10.0 → 0.10.1
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/.github/workflows/deploy-www.yml +47 -0
- package/ALGORITHM.md +108 -8
- package/CHANGELOG.md +10 -0
- package/dist/browser.js +518 -13
- package/dist/browser.js.map +1 -1
- package/dist/main.js +518 -13
- package/dist/main.js.map +1 -1
- package/dist/module.js +518 -13
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/archetypes.ts +4 -1
- package/src/lib/canvas/colors.ts +45 -0
- package/src/lib/canvas/draw.ts +84 -5
- package/src/lib/render.ts +255 -10
- package/src/lib/utils.ts +109 -0
package/dist/main.js
CHANGED
|
@@ -76,6 +76,136 @@ const $e4b03e131ed2a289$export$bb9e4790bc99ae59 = {
|
|
|
76
76
|
PI: Math.PI,
|
|
77
77
|
PHI: (1 + Math.sqrt(5)) / 2
|
|
78
78
|
};
|
|
79
|
+
function $e4b03e131ed2a289$export$bbde7fbaaf9a8d66(rng) {
|
|
80
|
+
// Build a deterministic permutation table (256 entries, doubled)
|
|
81
|
+
const perm = new Uint8Array(512);
|
|
82
|
+
const p = new Uint8Array(256);
|
|
83
|
+
for(let i = 0; i < 256; i++)p[i] = i;
|
|
84
|
+
// Fisher-Yates shuffle with our seeded RNG
|
|
85
|
+
for(let i = 255; i > 0; i--){
|
|
86
|
+
const j = Math.floor(rng() * (i + 1));
|
|
87
|
+
const tmp = p[i];
|
|
88
|
+
p[i] = p[j];
|
|
89
|
+
p[j] = tmp;
|
|
90
|
+
}
|
|
91
|
+
for(let i = 0; i < 512; i++)perm[i] = p[i & 255];
|
|
92
|
+
// 12 gradient vectors for 2D simplex
|
|
93
|
+
const GRAD2 = [
|
|
94
|
+
[
|
|
95
|
+
1,
|
|
96
|
+
1
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
-1,
|
|
100
|
+
1
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
1,
|
|
104
|
+
-1
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
-1,
|
|
108
|
+
-1
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
1,
|
|
112
|
+
0
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
-1,
|
|
116
|
+
0
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
0,
|
|
120
|
+
1
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
0,
|
|
124
|
+
-1
|
|
125
|
+
],
|
|
126
|
+
[
|
|
127
|
+
1,
|
|
128
|
+
1
|
|
129
|
+
],
|
|
130
|
+
[
|
|
131
|
+
-1,
|
|
132
|
+
1
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
1,
|
|
136
|
+
-1
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
-1,
|
|
140
|
+
-1
|
|
141
|
+
]
|
|
142
|
+
];
|
|
143
|
+
const F2 = 0.5 * (Math.sqrt(3) - 1);
|
|
144
|
+
const G2 = (3 - Math.sqrt(3)) / 6;
|
|
145
|
+
function dot2(g, x, y) {
|
|
146
|
+
return g[0] * x + g[1] * y;
|
|
147
|
+
}
|
|
148
|
+
return function noise2D(xin, yin) {
|
|
149
|
+
const s = (xin + yin) * F2;
|
|
150
|
+
const i = Math.floor(xin + s);
|
|
151
|
+
const j = Math.floor(yin + s);
|
|
152
|
+
const t = (i + j) * G2;
|
|
153
|
+
const X0 = i - t;
|
|
154
|
+
const Y0 = j - t;
|
|
155
|
+
const x0 = xin - X0;
|
|
156
|
+
const y0 = yin - Y0;
|
|
157
|
+
let i1, j1;
|
|
158
|
+
if (x0 > y0) {
|
|
159
|
+
i1 = 1;
|
|
160
|
+
j1 = 0;
|
|
161
|
+
} else {
|
|
162
|
+
i1 = 0;
|
|
163
|
+
j1 = 1;
|
|
164
|
+
}
|
|
165
|
+
const x1 = x0 - i1 + G2;
|
|
166
|
+
const y1 = y0 - j1 + G2;
|
|
167
|
+
const x2 = x0 - 1 + 2 * G2;
|
|
168
|
+
const y2 = y0 - 1 + 2 * G2;
|
|
169
|
+
const ii = i & 255;
|
|
170
|
+
const jj = j & 255;
|
|
171
|
+
let n0 = 0, n1 = 0, n2 = 0;
|
|
172
|
+
let t0 = 0.5 - x0 * x0 - y0 * y0;
|
|
173
|
+
if (t0 >= 0) {
|
|
174
|
+
t0 *= t0;
|
|
175
|
+
const gi0 = perm[ii + perm[jj]] % 12;
|
|
176
|
+
n0 = t0 * t0 * dot2(GRAD2[gi0], x0, y0);
|
|
177
|
+
}
|
|
178
|
+
let t1 = 0.5 - x1 * x1 - y1 * y1;
|
|
179
|
+
if (t1 >= 0) {
|
|
180
|
+
t1 *= t1;
|
|
181
|
+
const gi1 = perm[ii + i1 + perm[jj + j1]] % 12;
|
|
182
|
+
n1 = t1 * t1 * dot2(GRAD2[gi1], x1, y1);
|
|
183
|
+
}
|
|
184
|
+
let t2 = 0.5 - x2 * x2 - y2 * y2;
|
|
185
|
+
if (t2 >= 0) {
|
|
186
|
+
t2 *= t2;
|
|
187
|
+
const gi2 = perm[ii + 1 + perm[jj + 1]] % 12;
|
|
188
|
+
n2 = t2 * t2 * dot2(GRAD2[gi2], x2, y2);
|
|
189
|
+
}
|
|
190
|
+
// Scale to approximately [-1, 1]
|
|
191
|
+
return 70 * (n0 + n1 + n2);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function $e4b03e131ed2a289$export$c81d639e83a19b85(noise, octaves = 4, lacunarity = 2.0, gain = 0.5) {
|
|
195
|
+
return function fbm(x, y) {
|
|
196
|
+
let value = 0;
|
|
197
|
+
let amplitude = 1;
|
|
198
|
+
let frequency = 1;
|
|
199
|
+
let maxAmp = 0;
|
|
200
|
+
for(let i = 0; i < octaves; i++){
|
|
201
|
+
value += noise(x * frequency, y * frequency) * amplitude;
|
|
202
|
+
maxAmp += amplitude;
|
|
203
|
+
amplitude *= gain;
|
|
204
|
+
frequency *= lacunarity;
|
|
205
|
+
}
|
|
206
|
+
return value / maxAmp;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
79
209
|
class $e4b03e131ed2a289$export$da2372f11bc66b3f {
|
|
80
210
|
static getProportionalSize(baseSize, proportion) {
|
|
81
211
|
return baseSize * proportion;
|
|
@@ -277,6 +407,48 @@ class $d016ad53434219a1$export$ab958c550f521376 {
|
|
|
277
407
|
$d016ad53434219a1$var$hslToHex(baseHue, 0.7, 0.35)
|
|
278
408
|
];
|
|
279
409
|
}
|
|
410
|
+
case "split-complementary":
|
|
411
|
+
{
|
|
412
|
+
// Base hue + two colors flanking the complement (±30°)
|
|
413
|
+
const comp = (baseHue + 180) % 360;
|
|
414
|
+
const split1 = (comp - 30 + 360) % 360;
|
|
415
|
+
const split2 = (comp + 30) % 360;
|
|
416
|
+
const sat = 0.55 + this.rng() * 0.25;
|
|
417
|
+
return [
|
|
418
|
+
$d016ad53434219a1$var$hslToHex(baseHue, sat, 0.5),
|
|
419
|
+
$d016ad53434219a1$var$hslToHex(baseHue, sat * 0.8, 0.65),
|
|
420
|
+
$d016ad53434219a1$var$hslToHex(split1, sat, 0.5),
|
|
421
|
+
$d016ad53434219a1$var$hslToHex(split2, sat, 0.5),
|
|
422
|
+
$d016ad53434219a1$var$hslToHex(split1, sat * 0.7, 0.7)
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
case "analogous-accent":
|
|
426
|
+
{
|
|
427
|
+
// Tight cluster of 3 analogous hues + 1 distant accent
|
|
428
|
+
const step = 15 + this.rng() * 20; // 15-35° apart
|
|
429
|
+
const h1 = (baseHue - step + 360) % 360;
|
|
430
|
+
const h2 = (baseHue + step) % 360;
|
|
431
|
+
const accentHue = (baseHue + 150 + this.rng() * 60) % 360;
|
|
432
|
+
const sat = 0.5 + this.rng() * 0.3;
|
|
433
|
+
return [
|
|
434
|
+
$d016ad53434219a1$var$hslToHex(baseHue, sat, 0.5),
|
|
435
|
+
$d016ad53434219a1$var$hslToHex(h1, sat, 0.55),
|
|
436
|
+
$d016ad53434219a1$var$hslToHex(h2, sat, 0.45),
|
|
437
|
+
$d016ad53434219a1$var$hslToHex(accentHue, sat + 0.15, 0.5)
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
case "limited-palette":
|
|
441
|
+
{
|
|
442
|
+
// Only 3 colors — like a risograph print
|
|
443
|
+
const h2 = (baseHue + 120 + this.rng() * 40) % 360;
|
|
444
|
+
const h3 = (baseHue + 220 + this.rng() * 40) % 360;
|
|
445
|
+
const sat = 0.6 + this.rng() * 0.2;
|
|
446
|
+
return [
|
|
447
|
+
$d016ad53434219a1$var$hslToHex(baseHue, sat, 0.5),
|
|
448
|
+
$d016ad53434219a1$var$hslToHex(h2, sat, 0.5),
|
|
449
|
+
$d016ad53434219a1$var$hslToHex(h3, sat * 0.9, 0.55)
|
|
450
|
+
];
|
|
451
|
+
}
|
|
280
452
|
case "harmonious":
|
|
281
453
|
default:
|
|
282
454
|
return this.getColors();
|
|
@@ -297,6 +469,14 @@ class $d016ad53434219a1$export$ab958c550f521376 {
|
|
|
297
469
|
"#f5f5f0",
|
|
298
470
|
"#e8e8e0"
|
|
299
471
|
];
|
|
472
|
+
case "split-complementary":
|
|
473
|
+
case "analogous-accent":
|
|
474
|
+
return this.getBackgroundColors();
|
|
475
|
+
case "limited-palette":
|
|
476
|
+
return [
|
|
477
|
+
$d016ad53434219a1$var$hslToHex(this.seed % 360, 0.08, 0.94),
|
|
478
|
+
$d016ad53434219a1$var$hslToHex((this.seed + 20) % 360, 0.06, 0.90)
|
|
479
|
+
];
|
|
300
480
|
case "neon":
|
|
301
481
|
return [
|
|
302
482
|
"#0a0a12",
|
|
@@ -550,7 +730,8 @@ function $d016ad53434219a1$export$703ba40a4347f77a(base, layerRatio, hueShiftPer
|
|
|
550
730
|
return {
|
|
551
731
|
dominant: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.dominant, shift),
|
|
552
732
|
secondary: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.secondary, shift * 0.7),
|
|
553
|
-
accent: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5)
|
|
733
|
+
accent: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5),
|
|
734
|
+
all: base.all.map((c)=>$d016ad53434219a1$export$1793a1bfbe4f6ff5(c, shift * 0.6))
|
|
554
735
|
};
|
|
555
736
|
}
|
|
556
737
|
|
|
@@ -1933,6 +2114,23 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1933
2114
|
ctx.fill();
|
|
1934
2115
|
ctx.fillStyle = origFill;
|
|
1935
2116
|
ctx.restore();
|
|
2117
|
+
// Pass 4: Organic edge erosion — irregular bites along the boundary
|
|
2118
|
+
if (rng && size > 20) {
|
|
2119
|
+
const erosionBites = 6 + Math.floor(rng() * 8);
|
|
2120
|
+
const edgeRadius = size * 0.45;
|
|
2121
|
+
ctx.save();
|
|
2122
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2123
|
+
ctx.globalAlpha = 0.6 + rng() * 0.3;
|
|
2124
|
+
for(let eb = 0; eb < erosionBites; eb++){
|
|
2125
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
2126
|
+
const biteDist = edgeRadius * (0.85 + rng() * 0.25);
|
|
2127
|
+
const biteR = size * (0.02 + rng() * 0.04);
|
|
2128
|
+
ctx.beginPath();
|
|
2129
|
+
ctx.arc(Math.cos(biteAngle) * biteDist, Math.sin(biteAngle) * biteDist, biteR, 0, Math.PI * 2);
|
|
2130
|
+
ctx.fill();
|
|
2131
|
+
}
|
|
2132
|
+
ctx.restore();
|
|
2133
|
+
}
|
|
1936
2134
|
ctx.globalAlpha = savedAlpha;
|
|
1937
2135
|
// Soft stroke on top — thinner than normal for delicacy
|
|
1938
2136
|
ctx.globalAlpha *= 0.25;
|
|
@@ -2242,6 +2440,23 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2242
2440
|
ctx.stroke();
|
|
2243
2441
|
ctx.restore();
|
|
2244
2442
|
}
|
|
2443
|
+
// Organic edge erosion — small irregular bites for rough paper feel
|
|
2444
|
+
if (rng && size > 20) {
|
|
2445
|
+
const erosionBites = 4 + Math.floor(rng() * 6);
|
|
2446
|
+
const edgeRadius = size * 0.42;
|
|
2447
|
+
ctx.save();
|
|
2448
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2449
|
+
ctx.globalAlpha = 0.5 + rng() * 0.3;
|
|
2450
|
+
for(let eb = 0; eb < erosionBites; eb++){
|
|
2451
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
2452
|
+
const biteDist = edgeRadius * (0.9 + rng() * 0.2);
|
|
2453
|
+
const biteR = size * (0.015 + rng() * 0.03);
|
|
2454
|
+
ctx.beginPath();
|
|
2455
|
+
ctx.arc(Math.cos(biteAngle) * biteDist, Math.sin(biteAngle) * biteDist, biteR, 0, Math.PI * 2);
|
|
2456
|
+
ctx.fill();
|
|
2457
|
+
}
|
|
2458
|
+
ctx.restore();
|
|
2459
|
+
}
|
|
2245
2460
|
ctx.globalAlpha = savedAlphaHD;
|
|
2246
2461
|
break;
|
|
2247
2462
|
}
|
|
@@ -2253,12 +2468,20 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2253
2468
|
}
|
|
2254
2469
|
}
|
|
2255
2470
|
function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
2256
|
-
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, patterns: patterns = [], proportionType: proportionType = "GOLDEN_RATIO", baseOpacity: baseOpacity = 0.6, opacityReduction: opacityReduction = 0.1, glowRadius: glowRadius = 0, glowColor: glowColor, gradientFillEnd: gradientFillEnd, renderStyle: renderStyle = "fill-and-stroke", rng: rng } = config;
|
|
2471
|
+
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, patterns: patterns = [], proportionType: proportionType = "GOLDEN_RATIO", baseOpacity: baseOpacity = 0.6, opacityReduction: opacityReduction = 0.1, glowRadius: glowRadius = 0, glowColor: glowColor, gradientFillEnd: gradientFillEnd, renderStyle: renderStyle = "fill-and-stroke", rng: rng, lightAngle: lightAngle, scaleFactor: scaleFactor = 1 } = config;
|
|
2257
2472
|
ctx.save();
|
|
2258
2473
|
ctx.translate(x, y);
|
|
2259
2474
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2260
|
-
//
|
|
2261
|
-
if (
|
|
2475
|
+
// ── Drop shadow — soft colored shadow offset along light direction ──
|
|
2476
|
+
if (lightAngle !== undefined && size > 10) {
|
|
2477
|
+
const shadowDist = size * 0.035;
|
|
2478
|
+
const shadowBlurR = size * 0.06;
|
|
2479
|
+
ctx.shadowOffsetX = Math.cos(lightAngle + Math.PI) * shadowDist;
|
|
2480
|
+
ctx.shadowOffsetY = Math.sin(lightAngle + Math.PI) * shadowDist;
|
|
2481
|
+
ctx.shadowBlur = shadowBlurR;
|
|
2482
|
+
ctx.shadowColor = "rgba(0,0,0,0.12)";
|
|
2483
|
+
} else if (glowRadius > 0) {
|
|
2484
|
+
// Glow / shadow effect (legacy path)
|
|
2262
2485
|
ctx.shadowBlur = glowRadius;
|
|
2263
2486
|
ctx.shadowColor = glowColor || fillColor;
|
|
2264
2487
|
ctx.shadowOffsetX = 0;
|
|
@@ -2280,8 +2503,29 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2280
2503
|
});
|
|
2281
2504
|
$c3de8257a8baa3b0$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
2282
2505
|
}
|
|
2283
|
-
// Reset shadow so patterns aren't double-
|
|
2284
|
-
|
|
2506
|
+
// Reset shadow so patterns and highlight aren't double-shadowed
|
|
2507
|
+
ctx.shadowBlur = 0;
|
|
2508
|
+
ctx.shadowOffsetX = 0;
|
|
2509
|
+
ctx.shadowOffsetY = 0;
|
|
2510
|
+
ctx.shadowColor = "transparent";
|
|
2511
|
+
// ── Specular highlight — bright arc on the light-facing side ──
|
|
2512
|
+
if (lightAngle !== undefined && size > 15 && rng) {
|
|
2513
|
+
const hlRadius = size * 0.35;
|
|
2514
|
+
const hlDist = size * 0.15;
|
|
2515
|
+
const hlX = Math.cos(lightAngle) * hlDist;
|
|
2516
|
+
const hlY = Math.sin(lightAngle) * hlDist;
|
|
2517
|
+
const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, hlRadius);
|
|
2518
|
+
hlGrad.addColorStop(0, "rgba(255,255,255,0.18)");
|
|
2519
|
+
hlGrad.addColorStop(0.5, "rgba(255,255,255,0.05)");
|
|
2520
|
+
hlGrad.addColorStop(1, "rgba(255,255,255,0)");
|
|
2521
|
+
const savedOp = ctx.globalCompositeOperation;
|
|
2522
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
2523
|
+
ctx.fillStyle = hlGrad;
|
|
2524
|
+
ctx.beginPath();
|
|
2525
|
+
ctx.arc(hlX, hlY, hlRadius, 0, Math.PI * 2);
|
|
2526
|
+
ctx.fill();
|
|
2527
|
+
ctx.globalCompositeOperation = savedOp;
|
|
2528
|
+
}
|
|
2285
2529
|
// Layer additional patterns if specified
|
|
2286
2530
|
if (patterns.length > 0) (0, $e4b03e131ed2a289$export$da2372f11bc66b3f).layerPatterns(ctx, patterns, {
|
|
2287
2531
|
baseSize: size,
|
|
@@ -3745,7 +3989,8 @@ const $4f72c5a314eddf25$var$COMPOSITION_MODES = [
|
|
|
3745
3989
|
"flow-field",
|
|
3746
3990
|
"spiral",
|
|
3747
3991
|
"grid-subdivision",
|
|
3748
|
-
"clustered"
|
|
3992
|
+
"clustered",
|
|
3993
|
+
"golden-spiral"
|
|
3749
3994
|
];
|
|
3750
3995
|
// ── Helper: get position based on composition mode ──────────────────
|
|
3751
3996
|
function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
@@ -3803,6 +4048,21 @@ function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height,
|
|
|
3803
4048
|
x: rng() * width,
|
|
3804
4049
|
y: rng() * height
|
|
3805
4050
|
};
|
|
4051
|
+
case "golden-spiral":
|
|
4052
|
+
{
|
|
4053
|
+
// Logarithmic spiral: r = a * e^(b*theta), with golden angle spacing
|
|
4054
|
+
const PHI = (1 + Math.sqrt(5)) / 2;
|
|
4055
|
+
const goldenAngle = 2 * Math.PI / (PHI * PHI); // ~137.5° in radians
|
|
4056
|
+
const t = shapeIndex / totalShapes;
|
|
4057
|
+
const angle = shapeIndex * goldenAngle + rng() * 0.3;
|
|
4058
|
+
const maxR = Math.min(width, height) * 0.44;
|
|
4059
|
+
// Shapes spiral outward with sqrt distribution for even area coverage
|
|
4060
|
+
const r = Math.sqrt(t) * maxR + (rng() - 0.5) * maxR * 0.08;
|
|
4061
|
+
return {
|
|
4062
|
+
x: cx + Math.cos(angle) * r,
|
|
4063
|
+
y: cy + Math.sin(angle) * r
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
3806
4066
|
}
|
|
3807
4067
|
}
|
|
3808
4068
|
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
@@ -4271,11 +4531,24 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4271
4531
|
}
|
|
4272
4532
|
}
|
|
4273
4533
|
ctx.globalAlpha = 1;
|
|
4274
|
-
// ── 4. Flow field
|
|
4534
|
+
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4535
|
+
// Create a seeded simplex noise field (unique per hash)
|
|
4536
|
+
const noiseFieldRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 333));
|
|
4537
|
+
const simplexNoise = (0, $e4b03e131ed2a289$export$bbde7fbaaf9a8d66)(noiseFieldRng);
|
|
4538
|
+
const fbmNoise = (0, $e4b03e131ed2a289$export$c81d639e83a19b85)(simplexNoise, 3, 2.0, 0.5);
|
|
4275
4539
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
4276
|
-
const fieldFreq =
|
|
4540
|
+
const fieldFreq = 1.5 + rng() * 2.5; // noise sampling frequency
|
|
4277
4541
|
function flowAngle(x, y) {
|
|
4278
|
-
|
|
4542
|
+
// Sample FBM noise at the position, scaled by frequency
|
|
4543
|
+
const nx = x / width * fieldFreq;
|
|
4544
|
+
const ny = y / height * fieldFreq;
|
|
4545
|
+
return fieldAngleBase + fbmNoise(nx, ny) * Math.PI;
|
|
4546
|
+
}
|
|
4547
|
+
// Noise-based size modulation — shapes in "high noise" areas get scaled
|
|
4548
|
+
function noiseSizeModulation(x, y) {
|
|
4549
|
+
const n = simplexNoise(x / width * 3, y / height * 3);
|
|
4550
|
+
// Map [-1,1] to [0.7, 1.3] — subtle terrain-like size variation
|
|
4551
|
+
return 0.7 + (n + 1) * 0.3;
|
|
4279
4552
|
}
|
|
4280
4553
|
// Track all placed shapes for density checks and connecting curves
|
|
4281
4554
|
const shapePositions = [];
|
|
@@ -4309,7 +4582,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4309
4582
|
glowColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
4310
4583
|
gradientFillEnd: (0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
4311
4584
|
renderStyle: heroStyle,
|
|
4312
|
-
rng: rng
|
|
4585
|
+
rng: rng,
|
|
4586
|
+
lightAngle: lightAngle,
|
|
4587
|
+
scaleFactor: scaleFactor
|
|
4313
4588
|
});
|
|
4314
4589
|
heroCenter = {
|
|
4315
4590
|
x: heroFocal.x,
|
|
@@ -4368,7 +4643,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4368
4643
|
}
|
|
4369
4644
|
// Power distribution for size — archetype controls the curve
|
|
4370
4645
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
4371
|
-
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
4646
|
+
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale * noiseSizeModulation(x, y);
|
|
4372
4647
|
// Size fraction for affinity-aware shape selection
|
|
4373
4648
|
const sizeFraction = size / adjustedMaxSize;
|
|
4374
4649
|
// Palette-driven shape selection (replaces naive pickShape)
|
|
@@ -4471,7 +4746,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4471
4746
|
glowColor: hasGlow ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
4472
4747
|
gradientFillEnd: gradientEnd,
|
|
4473
4748
|
renderStyle: finalRenderStyle,
|
|
4474
|
-
rng: rng
|
|
4749
|
+
rng: rng,
|
|
4750
|
+
lightAngle: lightAngle,
|
|
4751
|
+
scaleFactor: scaleFactor
|
|
4475
4752
|
};
|
|
4476
4753
|
if (shouldMirror) (0, $c3de8257a8baa3b0$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4477
4754
|
...shapeConfig,
|
|
@@ -4602,6 +4879,64 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4602
4879
|
}
|
|
4603
4880
|
// Reset blend mode for post-processing passes
|
|
4604
4881
|
ctx.globalCompositeOperation = "source-over";
|
|
4882
|
+
// ── 5f. Layered masking / cutout portals ───────────────────────
|
|
4883
|
+
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
4884
|
+
// with a tinted background wash, creating a "peek through" effect.
|
|
4885
|
+
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
4886
|
+
const portalCount = 1 + Math.floor(rng() * 2);
|
|
4887
|
+
for(let p = 0; p < portalCount; p++){
|
|
4888
|
+
// Pick a position biased toward placed shapes
|
|
4889
|
+
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
4890
|
+
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
4891
|
+
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
4892
|
+
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
4893
|
+
// Pick a portal shape from the palette
|
|
4894
|
+
const portalShape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
4895
|
+
const portalRotation = rng() * 360;
|
|
4896
|
+
const portalAlpha = 0.6 + rng() * 0.35;
|
|
4897
|
+
ctx.save();
|
|
4898
|
+
ctx.translate(portalX, portalY);
|
|
4899
|
+
ctx.rotate(portalRotation * Math.PI / 180);
|
|
4900
|
+
// Step 1: Clip to the portal shape and fill with background wash
|
|
4901
|
+
ctx.beginPath();
|
|
4902
|
+
(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
4903
|
+
ctx.clip();
|
|
4904
|
+
// Fill the clipped region with a radial gradient from background colors
|
|
4905
|
+
const portalColor = (0, $d016ad53434219a1$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
4906
|
+
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
4907
|
+
portalGrad.addColorStop(0, portalColor);
|
|
4908
|
+
portalGrad.addColorStop(1, bgEnd);
|
|
4909
|
+
ctx.globalAlpha = portalAlpha;
|
|
4910
|
+
ctx.fillStyle = portalGrad;
|
|
4911
|
+
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
4912
|
+
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
4913
|
+
if (rng() < 0.5) {
|
|
4914
|
+
const dotCount = 3 + Math.floor(rng() * 5);
|
|
4915
|
+
ctx.globalAlpha = portalAlpha * 0.3;
|
|
4916
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
4917
|
+
for(let d = 0; d < dotCount; d++){
|
|
4918
|
+
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
4919
|
+
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
4920
|
+
const dr = (1 + rng() * 3) * scaleFactor;
|
|
4921
|
+
ctx.beginPath();
|
|
4922
|
+
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
4923
|
+
ctx.fill();
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
ctx.restore();
|
|
4927
|
+
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
4928
|
+
ctx.save();
|
|
4929
|
+
ctx.translate(portalX, portalY);
|
|
4930
|
+
ctx.rotate(portalRotation * Math.PI / 180);
|
|
4931
|
+
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
4932
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
4933
|
+
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
4934
|
+
ctx.beginPath();
|
|
4935
|
+
(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
4936
|
+
ctx.stroke();
|
|
4937
|
+
ctx.restore();
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4605
4940
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
4606
4941
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
4607
4942
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
@@ -4813,6 +5148,176 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4813
5148
|
ctx.restore();
|
|
4814
5149
|
ctx.globalCompositeOperation = "source-over";
|
|
4815
5150
|
}
|
|
5151
|
+
// 10d. Gradient map — map luminance through a two-color gradient
|
|
5152
|
+
// Uses dominant→accent as the dark→light ramp for a cohesive tonal look
|
|
5153
|
+
if (rng() < 0.35) {
|
|
5154
|
+
const gmDark = colorHierarchy.dominant;
|
|
5155
|
+
const gmLight = colorHierarchy.accent;
|
|
5156
|
+
ctx.globalAlpha = 0.06 + rng() * 0.06; // very subtle: 6-12%
|
|
5157
|
+
ctx.globalCompositeOperation = "color";
|
|
5158
|
+
// Paint a linear gradient from dark color (top) to light color (bottom)
|
|
5159
|
+
const gmGrad = ctx.createLinearGradient(0, 0, 0, height);
|
|
5160
|
+
gmGrad.addColorStop(0, gmDark);
|
|
5161
|
+
gmGrad.addColorStop(1, gmLight);
|
|
5162
|
+
ctx.fillStyle = gmGrad;
|
|
5163
|
+
ctx.fillRect(0, 0, width, height);
|
|
5164
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5165
|
+
}
|
|
5166
|
+
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5167
|
+
{
|
|
5168
|
+
ctx.save();
|
|
5169
|
+
ctx.globalAlpha = 1;
|
|
5170
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5171
|
+
const borderRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 314));
|
|
5172
|
+
const borderPad = Math.min(width, height) * 0.025;
|
|
5173
|
+
const borderColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
5174
|
+
const borderColorSolid = colorHierarchy.accent;
|
|
5175
|
+
const archName = archetype.name;
|
|
5176
|
+
if (archName.includes("geometric") || archName.includes("op-art") || archName.includes("shattered")) {
|
|
5177
|
+
// Clean ruled lines with corner ornaments
|
|
5178
|
+
ctx.strokeStyle = borderColor;
|
|
5179
|
+
ctx.lineWidth = Math.max(1, 1.5 * scaleFactor);
|
|
5180
|
+
ctx.globalAlpha = 0.18 + borderRng() * 0.1;
|
|
5181
|
+
// Outer rule
|
|
5182
|
+
ctx.strokeRect(borderPad, borderPad, width - borderPad * 2, height - borderPad * 2);
|
|
5183
|
+
// Inner rule (thinner, offset)
|
|
5184
|
+
const innerPad = borderPad * 1.8;
|
|
5185
|
+
ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
|
|
5186
|
+
ctx.globalAlpha *= 0.7;
|
|
5187
|
+
ctx.strokeRect(innerPad, innerPad, width - innerPad * 2, height - innerPad * 2);
|
|
5188
|
+
// Corner ornaments — small squares at each corner
|
|
5189
|
+
const ornSize = borderPad * 0.6;
|
|
5190
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(borderColorSolid, 0.12);
|
|
5191
|
+
const corners = [
|
|
5192
|
+
[
|
|
5193
|
+
borderPad,
|
|
5194
|
+
borderPad
|
|
5195
|
+
],
|
|
5196
|
+
[
|
|
5197
|
+
width - borderPad - ornSize,
|
|
5198
|
+
borderPad
|
|
5199
|
+
],
|
|
5200
|
+
[
|
|
5201
|
+
borderPad,
|
|
5202
|
+
height - borderPad - ornSize
|
|
5203
|
+
],
|
|
5204
|
+
[
|
|
5205
|
+
width - borderPad - ornSize,
|
|
5206
|
+
height - borderPad - ornSize
|
|
5207
|
+
]
|
|
5208
|
+
];
|
|
5209
|
+
for (const [cx2, cy2] of corners){
|
|
5210
|
+
ctx.fillRect(cx2, cy2, ornSize, ornSize);
|
|
5211
|
+
// Diagonal cross inside ornament
|
|
5212
|
+
ctx.beginPath();
|
|
5213
|
+
ctx.moveTo(cx2, cy2);
|
|
5214
|
+
ctx.lineTo(cx2 + ornSize, cy2 + ornSize);
|
|
5215
|
+
ctx.moveTo(cx2 + ornSize, cy2);
|
|
5216
|
+
ctx.lineTo(cx2, cy2 + ornSize);
|
|
5217
|
+
ctx.stroke();
|
|
5218
|
+
}
|
|
5219
|
+
} else if (archName.includes("botanical") || archName.includes("organic") || archName.includes("watercolor")) {
|
|
5220
|
+
// Vine tendrils — organic curving lines along edges
|
|
5221
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
5222
|
+
ctx.lineWidth = Math.max(0.8, 1.2 * scaleFactor);
|
|
5223
|
+
ctx.globalAlpha = 0.12 + borderRng() * 0.08;
|
|
5224
|
+
ctx.lineCap = "round";
|
|
5225
|
+
const tendrilCount = 8 + Math.floor(borderRng() * 8);
|
|
5226
|
+
for(let t = 0; t < tendrilCount; t++){
|
|
5227
|
+
// Start from a random edge point
|
|
5228
|
+
const edge = Math.floor(borderRng() * 4);
|
|
5229
|
+
let tx, ty;
|
|
5230
|
+
if (edge === 0) {
|
|
5231
|
+
tx = borderRng() * width;
|
|
5232
|
+
ty = borderPad;
|
|
5233
|
+
} else if (edge === 1) {
|
|
5234
|
+
tx = borderRng() * width;
|
|
5235
|
+
ty = height - borderPad;
|
|
5236
|
+
} else if (edge === 2) {
|
|
5237
|
+
tx = borderPad;
|
|
5238
|
+
ty = borderRng() * height;
|
|
5239
|
+
} else {
|
|
5240
|
+
tx = width - borderPad;
|
|
5241
|
+
ty = borderRng() * height;
|
|
5242
|
+
}
|
|
5243
|
+
ctx.beginPath();
|
|
5244
|
+
ctx.moveTo(tx, ty);
|
|
5245
|
+
const segs = 3 + Math.floor(borderRng() * 4);
|
|
5246
|
+
for(let s = 0; s < segs; s++){
|
|
5247
|
+
const inward = borderPad * (1 + borderRng() * 2);
|
|
5248
|
+
// Curl inward from edge
|
|
5249
|
+
const cpx2 = tx + (borderRng() - 0.5) * borderPad * 4;
|
|
5250
|
+
const cpy2 = ty + (edge < 2 ? edge === 0 ? inward : -inward : 0);
|
|
5251
|
+
const cpx3 = tx + (edge >= 2 ? edge === 2 ? inward : -inward : (borderRng() - 0.5) * borderPad * 3);
|
|
5252
|
+
const cpy3 = ty + (borderRng() - 0.5) * borderPad * 3;
|
|
5253
|
+
tx = cpx3;
|
|
5254
|
+
ty = cpy3;
|
|
5255
|
+
ctx.quadraticCurveTo(cpx2, cpy2, tx, ty);
|
|
5256
|
+
}
|
|
5257
|
+
ctx.stroke();
|
|
5258
|
+
// Small leaf/dot at tendril end
|
|
5259
|
+
if (borderRng() < 0.6) {
|
|
5260
|
+
ctx.beginPath();
|
|
5261
|
+
ctx.arc(tx, ty, borderPad * (0.15 + borderRng() * 0.2), 0, Math.PI * 2);
|
|
5262
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.08);
|
|
5263
|
+
ctx.fill();
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
} else if (archName.includes("celestial") || archName.includes("cosmic") || archName.includes("neon")) {
|
|
5267
|
+
// Star-studded arcs along edges
|
|
5268
|
+
ctx.globalAlpha = 0.1 + borderRng() * 0.08;
|
|
5269
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
5270
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.accent, 0.12);
|
|
5271
|
+
ctx.lineWidth = Math.max(0.5, 0.7 * scaleFactor);
|
|
5272
|
+
// Subtle arc along top and bottom
|
|
5273
|
+
ctx.beginPath();
|
|
5274
|
+
ctx.arc(cx, -height * 0.3, height * 0.6, 0.3, Math.PI - 0.3);
|
|
5275
|
+
ctx.stroke();
|
|
5276
|
+
ctx.beginPath();
|
|
5277
|
+
ctx.arc(cx, height * 1.3, height * 0.6, Math.PI + 0.3, -0.3);
|
|
5278
|
+
ctx.stroke();
|
|
5279
|
+
// Scatter small stars along the border region
|
|
5280
|
+
const starCount = 15 + Math.floor(borderRng() * 15);
|
|
5281
|
+
for(let s = 0; s < starCount; s++){
|
|
5282
|
+
const edge = Math.floor(borderRng() * 4);
|
|
5283
|
+
let sx, sy;
|
|
5284
|
+
if (edge === 0) {
|
|
5285
|
+
sx = borderRng() * width;
|
|
5286
|
+
sy = borderPad * (0.5 + borderRng());
|
|
5287
|
+
} else if (edge === 1) {
|
|
5288
|
+
sx = borderRng() * width;
|
|
5289
|
+
sy = height - borderPad * (0.5 + borderRng());
|
|
5290
|
+
} else if (edge === 2) {
|
|
5291
|
+
sx = borderPad * (0.5 + borderRng());
|
|
5292
|
+
sy = borderRng() * height;
|
|
5293
|
+
} else {
|
|
5294
|
+
sx = width - borderPad * (0.5 + borderRng());
|
|
5295
|
+
sy = borderRng() * height;
|
|
5296
|
+
}
|
|
5297
|
+
const starR = (1 + borderRng() * 2.5) * scaleFactor;
|
|
5298
|
+
// 4-point star
|
|
5299
|
+
ctx.beginPath();
|
|
5300
|
+
for(let p = 0; p < 8; p++){
|
|
5301
|
+
const a = p / 8 * Math.PI * 2;
|
|
5302
|
+
const r = p % 2 === 0 ? starR : starR * 0.4;
|
|
5303
|
+
const px2 = sx + Math.cos(a) * r;
|
|
5304
|
+
const py2 = sy + Math.sin(a) * r;
|
|
5305
|
+
if (p === 0) ctx.moveTo(px2, py2);
|
|
5306
|
+
else ctx.lineTo(px2, py2);
|
|
5307
|
+
}
|
|
5308
|
+
ctx.closePath();
|
|
5309
|
+
ctx.fill();
|
|
5310
|
+
}
|
|
5311
|
+
} else if (archName.includes("minimal") || archName.includes("monochrome") || archName.includes("stipple")) {
|
|
5312
|
+
// Thin single rule — understated elegance
|
|
5313
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
5314
|
+
ctx.lineWidth = Math.max(0.5, 0.6 * scaleFactor);
|
|
5315
|
+
ctx.globalAlpha = 0.1 + borderRng() * 0.06;
|
|
5316
|
+
ctx.strokeRect(borderPad * 1.5, borderPad * 1.5, width - borderPad * 3, height - borderPad * 3);
|
|
5317
|
+
}
|
|
5318
|
+
// Other archetypes: no border (intentional — not every image needs one)
|
|
5319
|
+
ctx.restore();
|
|
5320
|
+
}
|
|
4816
5321
|
// ── 11. Signature mark — unique geometric chop from hash prefix ──
|
|
4817
5322
|
{
|
|
4818
5323
|
const sigRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 42));
|