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/module.js
CHANGED
|
@@ -62,6 +62,136 @@ const $461134e0b6ce0619$export$bb9e4790bc99ae59 = {
|
|
|
62
62
|
PI: Math.PI,
|
|
63
63
|
PHI: (1 + Math.sqrt(5)) / 2
|
|
64
64
|
};
|
|
65
|
+
function $461134e0b6ce0619$export$bbde7fbaaf9a8d66(rng) {
|
|
66
|
+
// Build a deterministic permutation table (256 entries, doubled)
|
|
67
|
+
const perm = new Uint8Array(512);
|
|
68
|
+
const p = new Uint8Array(256);
|
|
69
|
+
for(let i = 0; i < 256; i++)p[i] = i;
|
|
70
|
+
// Fisher-Yates shuffle with our seeded RNG
|
|
71
|
+
for(let i = 255; i > 0; i--){
|
|
72
|
+
const j = Math.floor(rng() * (i + 1));
|
|
73
|
+
const tmp = p[i];
|
|
74
|
+
p[i] = p[j];
|
|
75
|
+
p[j] = tmp;
|
|
76
|
+
}
|
|
77
|
+
for(let i = 0; i < 512; i++)perm[i] = p[i & 255];
|
|
78
|
+
// 12 gradient vectors for 2D simplex
|
|
79
|
+
const GRAD2 = [
|
|
80
|
+
[
|
|
81
|
+
1,
|
|
82
|
+
1
|
|
83
|
+
],
|
|
84
|
+
[
|
|
85
|
+
-1,
|
|
86
|
+
1
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
1,
|
|
90
|
+
-1
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
-1,
|
|
94
|
+
-1
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
1,
|
|
98
|
+
0
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
-1,
|
|
102
|
+
0
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
0,
|
|
106
|
+
1
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
0,
|
|
110
|
+
-1
|
|
111
|
+
],
|
|
112
|
+
[
|
|
113
|
+
1,
|
|
114
|
+
1
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
-1,
|
|
118
|
+
1
|
|
119
|
+
],
|
|
120
|
+
[
|
|
121
|
+
1,
|
|
122
|
+
-1
|
|
123
|
+
],
|
|
124
|
+
[
|
|
125
|
+
-1,
|
|
126
|
+
-1
|
|
127
|
+
]
|
|
128
|
+
];
|
|
129
|
+
const F2 = 0.5 * (Math.sqrt(3) - 1);
|
|
130
|
+
const G2 = (3 - Math.sqrt(3)) / 6;
|
|
131
|
+
function dot2(g, x, y) {
|
|
132
|
+
return g[0] * x + g[1] * y;
|
|
133
|
+
}
|
|
134
|
+
return function noise2D(xin, yin) {
|
|
135
|
+
const s = (xin + yin) * F2;
|
|
136
|
+
const i = Math.floor(xin + s);
|
|
137
|
+
const j = Math.floor(yin + s);
|
|
138
|
+
const t = (i + j) * G2;
|
|
139
|
+
const X0 = i - t;
|
|
140
|
+
const Y0 = j - t;
|
|
141
|
+
const x0 = xin - X0;
|
|
142
|
+
const y0 = yin - Y0;
|
|
143
|
+
let i1, j1;
|
|
144
|
+
if (x0 > y0) {
|
|
145
|
+
i1 = 1;
|
|
146
|
+
j1 = 0;
|
|
147
|
+
} else {
|
|
148
|
+
i1 = 0;
|
|
149
|
+
j1 = 1;
|
|
150
|
+
}
|
|
151
|
+
const x1 = x0 - i1 + G2;
|
|
152
|
+
const y1 = y0 - j1 + G2;
|
|
153
|
+
const x2 = x0 - 1 + 2 * G2;
|
|
154
|
+
const y2 = y0 - 1 + 2 * G2;
|
|
155
|
+
const ii = i & 255;
|
|
156
|
+
const jj = j & 255;
|
|
157
|
+
let n0 = 0, n1 = 0, n2 = 0;
|
|
158
|
+
let t0 = 0.5 - x0 * x0 - y0 * y0;
|
|
159
|
+
if (t0 >= 0) {
|
|
160
|
+
t0 *= t0;
|
|
161
|
+
const gi0 = perm[ii + perm[jj]] % 12;
|
|
162
|
+
n0 = t0 * t0 * dot2(GRAD2[gi0], x0, y0);
|
|
163
|
+
}
|
|
164
|
+
let t1 = 0.5 - x1 * x1 - y1 * y1;
|
|
165
|
+
if (t1 >= 0) {
|
|
166
|
+
t1 *= t1;
|
|
167
|
+
const gi1 = perm[ii + i1 + perm[jj + j1]] % 12;
|
|
168
|
+
n1 = t1 * t1 * dot2(GRAD2[gi1], x1, y1);
|
|
169
|
+
}
|
|
170
|
+
let t2 = 0.5 - x2 * x2 - y2 * y2;
|
|
171
|
+
if (t2 >= 0) {
|
|
172
|
+
t2 *= t2;
|
|
173
|
+
const gi2 = perm[ii + 1 + perm[jj + 1]] % 12;
|
|
174
|
+
n2 = t2 * t2 * dot2(GRAD2[gi2], x2, y2);
|
|
175
|
+
}
|
|
176
|
+
// Scale to approximately [-1, 1]
|
|
177
|
+
return 70 * (n0 + n1 + n2);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function $461134e0b6ce0619$export$c81d639e83a19b85(noise, octaves = 4, lacunarity = 2.0, gain = 0.5) {
|
|
181
|
+
return function fbm(x, y) {
|
|
182
|
+
let value = 0;
|
|
183
|
+
let amplitude = 1;
|
|
184
|
+
let frequency = 1;
|
|
185
|
+
let maxAmp = 0;
|
|
186
|
+
for(let i = 0; i < octaves; i++){
|
|
187
|
+
value += noise(x * frequency, y * frequency) * amplitude;
|
|
188
|
+
maxAmp += amplitude;
|
|
189
|
+
amplitude *= gain;
|
|
190
|
+
frequency *= lacunarity;
|
|
191
|
+
}
|
|
192
|
+
return value / maxAmp;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
65
195
|
class $461134e0b6ce0619$export$da2372f11bc66b3f {
|
|
66
196
|
static getProportionalSize(baseSize, proportion) {
|
|
67
197
|
return baseSize * proportion;
|
|
@@ -263,6 +393,48 @@ class $9d614e7d77fc2947$export$ab958c550f521376 {
|
|
|
263
393
|
$9d614e7d77fc2947$var$hslToHex(baseHue, 0.7, 0.35)
|
|
264
394
|
];
|
|
265
395
|
}
|
|
396
|
+
case "split-complementary":
|
|
397
|
+
{
|
|
398
|
+
// Base hue + two colors flanking the complement (±30°)
|
|
399
|
+
const comp = (baseHue + 180) % 360;
|
|
400
|
+
const split1 = (comp - 30 + 360) % 360;
|
|
401
|
+
const split2 = (comp + 30) % 360;
|
|
402
|
+
const sat = 0.55 + this.rng() * 0.25;
|
|
403
|
+
return [
|
|
404
|
+
$9d614e7d77fc2947$var$hslToHex(baseHue, sat, 0.5),
|
|
405
|
+
$9d614e7d77fc2947$var$hslToHex(baseHue, sat * 0.8, 0.65),
|
|
406
|
+
$9d614e7d77fc2947$var$hslToHex(split1, sat, 0.5),
|
|
407
|
+
$9d614e7d77fc2947$var$hslToHex(split2, sat, 0.5),
|
|
408
|
+
$9d614e7d77fc2947$var$hslToHex(split1, sat * 0.7, 0.7)
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
case "analogous-accent":
|
|
412
|
+
{
|
|
413
|
+
// Tight cluster of 3 analogous hues + 1 distant accent
|
|
414
|
+
const step = 15 + this.rng() * 20; // 15-35° apart
|
|
415
|
+
const h1 = (baseHue - step + 360) % 360;
|
|
416
|
+
const h2 = (baseHue + step) % 360;
|
|
417
|
+
const accentHue = (baseHue + 150 + this.rng() * 60) % 360;
|
|
418
|
+
const sat = 0.5 + this.rng() * 0.3;
|
|
419
|
+
return [
|
|
420
|
+
$9d614e7d77fc2947$var$hslToHex(baseHue, sat, 0.5),
|
|
421
|
+
$9d614e7d77fc2947$var$hslToHex(h1, sat, 0.55),
|
|
422
|
+
$9d614e7d77fc2947$var$hslToHex(h2, sat, 0.45),
|
|
423
|
+
$9d614e7d77fc2947$var$hslToHex(accentHue, sat + 0.15, 0.5)
|
|
424
|
+
];
|
|
425
|
+
}
|
|
426
|
+
case "limited-palette":
|
|
427
|
+
{
|
|
428
|
+
// Only 3 colors — like a risograph print
|
|
429
|
+
const h2 = (baseHue + 120 + this.rng() * 40) % 360;
|
|
430
|
+
const h3 = (baseHue + 220 + this.rng() * 40) % 360;
|
|
431
|
+
const sat = 0.6 + this.rng() * 0.2;
|
|
432
|
+
return [
|
|
433
|
+
$9d614e7d77fc2947$var$hslToHex(baseHue, sat, 0.5),
|
|
434
|
+
$9d614e7d77fc2947$var$hslToHex(h2, sat, 0.5),
|
|
435
|
+
$9d614e7d77fc2947$var$hslToHex(h3, sat * 0.9, 0.55)
|
|
436
|
+
];
|
|
437
|
+
}
|
|
266
438
|
case "harmonious":
|
|
267
439
|
default:
|
|
268
440
|
return this.getColors();
|
|
@@ -283,6 +455,14 @@ class $9d614e7d77fc2947$export$ab958c550f521376 {
|
|
|
283
455
|
"#f5f5f0",
|
|
284
456
|
"#e8e8e0"
|
|
285
457
|
];
|
|
458
|
+
case "split-complementary":
|
|
459
|
+
case "analogous-accent":
|
|
460
|
+
return this.getBackgroundColors();
|
|
461
|
+
case "limited-palette":
|
|
462
|
+
return [
|
|
463
|
+
$9d614e7d77fc2947$var$hslToHex(this.seed % 360, 0.08, 0.94),
|
|
464
|
+
$9d614e7d77fc2947$var$hslToHex((this.seed + 20) % 360, 0.06, 0.90)
|
|
465
|
+
];
|
|
286
466
|
case "neon":
|
|
287
467
|
return [
|
|
288
468
|
"#0a0a12",
|
|
@@ -536,7 +716,8 @@ function $9d614e7d77fc2947$export$703ba40a4347f77a(base, layerRatio, hueShiftPer
|
|
|
536
716
|
return {
|
|
537
717
|
dominant: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.dominant, shift),
|
|
538
718
|
secondary: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.secondary, shift * 0.7),
|
|
539
|
-
accent: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5)
|
|
719
|
+
accent: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5),
|
|
720
|
+
all: base.all.map((c)=>$9d614e7d77fc2947$export$1793a1bfbe4f6ff5(c, shift * 0.6))
|
|
540
721
|
};
|
|
541
722
|
}
|
|
542
723
|
|
|
@@ -1919,6 +2100,23 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1919
2100
|
ctx.fill();
|
|
1920
2101
|
ctx.fillStyle = origFill;
|
|
1921
2102
|
ctx.restore();
|
|
2103
|
+
// Pass 4: Organic edge erosion — irregular bites along the boundary
|
|
2104
|
+
if (rng && size > 20) {
|
|
2105
|
+
const erosionBites = 6 + Math.floor(rng() * 8);
|
|
2106
|
+
const edgeRadius = size * 0.45;
|
|
2107
|
+
ctx.save();
|
|
2108
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2109
|
+
ctx.globalAlpha = 0.6 + rng() * 0.3;
|
|
2110
|
+
for(let eb = 0; eb < erosionBites; eb++){
|
|
2111
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
2112
|
+
const biteDist = edgeRadius * (0.85 + rng() * 0.25);
|
|
2113
|
+
const biteR = size * (0.02 + rng() * 0.04);
|
|
2114
|
+
ctx.beginPath();
|
|
2115
|
+
ctx.arc(Math.cos(biteAngle) * biteDist, Math.sin(biteAngle) * biteDist, biteR, 0, Math.PI * 2);
|
|
2116
|
+
ctx.fill();
|
|
2117
|
+
}
|
|
2118
|
+
ctx.restore();
|
|
2119
|
+
}
|
|
1922
2120
|
ctx.globalAlpha = savedAlpha;
|
|
1923
2121
|
// Soft stroke on top — thinner than normal for delicacy
|
|
1924
2122
|
ctx.globalAlpha *= 0.25;
|
|
@@ -2228,6 +2426,23 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2228
2426
|
ctx.stroke();
|
|
2229
2427
|
ctx.restore();
|
|
2230
2428
|
}
|
|
2429
|
+
// Organic edge erosion — small irregular bites for rough paper feel
|
|
2430
|
+
if (rng && size > 20) {
|
|
2431
|
+
const erosionBites = 4 + Math.floor(rng() * 6);
|
|
2432
|
+
const edgeRadius = size * 0.42;
|
|
2433
|
+
ctx.save();
|
|
2434
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2435
|
+
ctx.globalAlpha = 0.5 + rng() * 0.3;
|
|
2436
|
+
for(let eb = 0; eb < erosionBites; eb++){
|
|
2437
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
2438
|
+
const biteDist = edgeRadius * (0.9 + rng() * 0.2);
|
|
2439
|
+
const biteR = size * (0.015 + rng() * 0.03);
|
|
2440
|
+
ctx.beginPath();
|
|
2441
|
+
ctx.arc(Math.cos(biteAngle) * biteDist, Math.sin(biteAngle) * biteDist, biteR, 0, Math.PI * 2);
|
|
2442
|
+
ctx.fill();
|
|
2443
|
+
}
|
|
2444
|
+
ctx.restore();
|
|
2445
|
+
}
|
|
2231
2446
|
ctx.globalAlpha = savedAlphaHD;
|
|
2232
2447
|
break;
|
|
2233
2448
|
}
|
|
@@ -2239,12 +2454,20 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2239
2454
|
}
|
|
2240
2455
|
}
|
|
2241
2456
|
function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
2242
|
-
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;
|
|
2457
|
+
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;
|
|
2243
2458
|
ctx.save();
|
|
2244
2459
|
ctx.translate(x, y);
|
|
2245
2460
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2246
|
-
//
|
|
2247
|
-
if (
|
|
2461
|
+
// ── Drop shadow — soft colored shadow offset along light direction ──
|
|
2462
|
+
if (lightAngle !== undefined && size > 10) {
|
|
2463
|
+
const shadowDist = size * 0.035;
|
|
2464
|
+
const shadowBlurR = size * 0.06;
|
|
2465
|
+
ctx.shadowOffsetX = Math.cos(lightAngle + Math.PI) * shadowDist;
|
|
2466
|
+
ctx.shadowOffsetY = Math.sin(lightAngle + Math.PI) * shadowDist;
|
|
2467
|
+
ctx.shadowBlur = shadowBlurR;
|
|
2468
|
+
ctx.shadowColor = "rgba(0,0,0,0.12)";
|
|
2469
|
+
} else if (glowRadius > 0) {
|
|
2470
|
+
// Glow / shadow effect (legacy path)
|
|
2248
2471
|
ctx.shadowBlur = glowRadius;
|
|
2249
2472
|
ctx.shadowColor = glowColor || fillColor;
|
|
2250
2473
|
ctx.shadowOffsetX = 0;
|
|
@@ -2266,8 +2489,29 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2266
2489
|
});
|
|
2267
2490
|
$9beb8f41637c29fd$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
2268
2491
|
}
|
|
2269
|
-
// Reset shadow so patterns aren't double-
|
|
2270
|
-
|
|
2492
|
+
// Reset shadow so patterns and highlight aren't double-shadowed
|
|
2493
|
+
ctx.shadowBlur = 0;
|
|
2494
|
+
ctx.shadowOffsetX = 0;
|
|
2495
|
+
ctx.shadowOffsetY = 0;
|
|
2496
|
+
ctx.shadowColor = "transparent";
|
|
2497
|
+
// ── Specular highlight — bright arc on the light-facing side ──
|
|
2498
|
+
if (lightAngle !== undefined && size > 15 && rng) {
|
|
2499
|
+
const hlRadius = size * 0.35;
|
|
2500
|
+
const hlDist = size * 0.15;
|
|
2501
|
+
const hlX = Math.cos(lightAngle) * hlDist;
|
|
2502
|
+
const hlY = Math.sin(lightAngle) * hlDist;
|
|
2503
|
+
const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, hlRadius);
|
|
2504
|
+
hlGrad.addColorStop(0, "rgba(255,255,255,0.18)");
|
|
2505
|
+
hlGrad.addColorStop(0.5, "rgba(255,255,255,0.05)");
|
|
2506
|
+
hlGrad.addColorStop(1, "rgba(255,255,255,0)");
|
|
2507
|
+
const savedOp = ctx.globalCompositeOperation;
|
|
2508
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
2509
|
+
ctx.fillStyle = hlGrad;
|
|
2510
|
+
ctx.beginPath();
|
|
2511
|
+
ctx.arc(hlX, hlY, hlRadius, 0, Math.PI * 2);
|
|
2512
|
+
ctx.fill();
|
|
2513
|
+
ctx.globalCompositeOperation = savedOp;
|
|
2514
|
+
}
|
|
2271
2515
|
// Layer additional patterns if specified
|
|
2272
2516
|
if (patterns.length > 0) (0, $461134e0b6ce0619$export$da2372f11bc66b3f).layerPatterns(ctx, patterns, {
|
|
2273
2517
|
baseSize: size,
|
|
@@ -3731,7 +3975,8 @@ const $b623126c6e9cbb71$var$COMPOSITION_MODES = [
|
|
|
3731
3975
|
"flow-field",
|
|
3732
3976
|
"spiral",
|
|
3733
3977
|
"grid-subdivision",
|
|
3734
|
-
"clustered"
|
|
3978
|
+
"clustered",
|
|
3979
|
+
"golden-spiral"
|
|
3735
3980
|
];
|
|
3736
3981
|
// ── Helper: get position based on composition mode ──────────────────
|
|
3737
3982
|
function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
@@ -3789,6 +4034,21 @@ function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height,
|
|
|
3789
4034
|
x: rng() * width,
|
|
3790
4035
|
y: rng() * height
|
|
3791
4036
|
};
|
|
4037
|
+
case "golden-spiral":
|
|
4038
|
+
{
|
|
4039
|
+
// Logarithmic spiral: r = a * e^(b*theta), with golden angle spacing
|
|
4040
|
+
const PHI = (1 + Math.sqrt(5)) / 2;
|
|
4041
|
+
const goldenAngle = 2 * Math.PI / (PHI * PHI); // ~137.5° in radians
|
|
4042
|
+
const t = shapeIndex / totalShapes;
|
|
4043
|
+
const angle = shapeIndex * goldenAngle + rng() * 0.3;
|
|
4044
|
+
const maxR = Math.min(width, height) * 0.44;
|
|
4045
|
+
// Shapes spiral outward with sqrt distribution for even area coverage
|
|
4046
|
+
const r = Math.sqrt(t) * maxR + (rng() - 0.5) * maxR * 0.08;
|
|
4047
|
+
return {
|
|
4048
|
+
x: cx + Math.cos(angle) * r,
|
|
4049
|
+
y: cy + Math.sin(angle) * r
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
3792
4052
|
}
|
|
3793
4053
|
}
|
|
3794
4054
|
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
@@ -4257,11 +4517,24 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4257
4517
|
}
|
|
4258
4518
|
}
|
|
4259
4519
|
ctx.globalAlpha = 1;
|
|
4260
|
-
// ── 4. Flow field
|
|
4520
|
+
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4521
|
+
// Create a seeded simplex noise field (unique per hash)
|
|
4522
|
+
const noiseFieldRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 333));
|
|
4523
|
+
const simplexNoise = (0, $461134e0b6ce0619$export$bbde7fbaaf9a8d66)(noiseFieldRng);
|
|
4524
|
+
const fbmNoise = (0, $461134e0b6ce0619$export$c81d639e83a19b85)(simplexNoise, 3, 2.0, 0.5);
|
|
4261
4525
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
4262
|
-
const fieldFreq =
|
|
4526
|
+
const fieldFreq = 1.5 + rng() * 2.5; // noise sampling frequency
|
|
4263
4527
|
function flowAngle(x, y) {
|
|
4264
|
-
|
|
4528
|
+
// Sample FBM noise at the position, scaled by frequency
|
|
4529
|
+
const nx = x / width * fieldFreq;
|
|
4530
|
+
const ny = y / height * fieldFreq;
|
|
4531
|
+
return fieldAngleBase + fbmNoise(nx, ny) * Math.PI;
|
|
4532
|
+
}
|
|
4533
|
+
// Noise-based size modulation — shapes in "high noise" areas get scaled
|
|
4534
|
+
function noiseSizeModulation(x, y) {
|
|
4535
|
+
const n = simplexNoise(x / width * 3, y / height * 3);
|
|
4536
|
+
// Map [-1,1] to [0.7, 1.3] — subtle terrain-like size variation
|
|
4537
|
+
return 0.7 + (n + 1) * 0.3;
|
|
4265
4538
|
}
|
|
4266
4539
|
// Track all placed shapes for density checks and connecting curves
|
|
4267
4540
|
const shapePositions = [];
|
|
@@ -4295,7 +4568,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4295
4568
|
glowColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
4296
4569
|
gradientFillEnd: (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
4297
4570
|
renderStyle: heroStyle,
|
|
4298
|
-
rng: rng
|
|
4571
|
+
rng: rng,
|
|
4572
|
+
lightAngle: lightAngle,
|
|
4573
|
+
scaleFactor: scaleFactor
|
|
4299
4574
|
});
|
|
4300
4575
|
heroCenter = {
|
|
4301
4576
|
x: heroFocal.x,
|
|
@@ -4354,7 +4629,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4354
4629
|
}
|
|
4355
4630
|
// Power distribution for size — archetype controls the curve
|
|
4356
4631
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
4357
|
-
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
4632
|
+
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale * noiseSizeModulation(x, y);
|
|
4358
4633
|
// Size fraction for affinity-aware shape selection
|
|
4359
4634
|
const sizeFraction = size / adjustedMaxSize;
|
|
4360
4635
|
// Palette-driven shape selection (replaces naive pickShape)
|
|
@@ -4457,7 +4732,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4457
4732
|
glowColor: hasGlow ? (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
4458
4733
|
gradientFillEnd: gradientEnd,
|
|
4459
4734
|
renderStyle: finalRenderStyle,
|
|
4460
|
-
rng: rng
|
|
4735
|
+
rng: rng,
|
|
4736
|
+
lightAngle: lightAngle,
|
|
4737
|
+
scaleFactor: scaleFactor
|
|
4461
4738
|
};
|
|
4462
4739
|
if (shouldMirror) (0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4463
4740
|
...shapeConfig,
|
|
@@ -4588,6 +4865,64 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4588
4865
|
}
|
|
4589
4866
|
// Reset blend mode for post-processing passes
|
|
4590
4867
|
ctx.globalCompositeOperation = "source-over";
|
|
4868
|
+
// ── 5f. Layered masking / cutout portals ───────────────────────
|
|
4869
|
+
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
4870
|
+
// with a tinted background wash, creating a "peek through" effect.
|
|
4871
|
+
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
4872
|
+
const portalCount = 1 + Math.floor(rng() * 2);
|
|
4873
|
+
for(let p = 0; p < portalCount; p++){
|
|
4874
|
+
// Pick a position biased toward placed shapes
|
|
4875
|
+
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
4876
|
+
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
4877
|
+
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
4878
|
+
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
4879
|
+
// Pick a portal shape from the palette
|
|
4880
|
+
const portalShape = (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
4881
|
+
const portalRotation = rng() * 360;
|
|
4882
|
+
const portalAlpha = 0.6 + rng() * 0.35;
|
|
4883
|
+
ctx.save();
|
|
4884
|
+
ctx.translate(portalX, portalY);
|
|
4885
|
+
ctx.rotate(portalRotation * Math.PI / 180);
|
|
4886
|
+
// Step 1: Clip to the portal shape and fill with background wash
|
|
4887
|
+
ctx.beginPath();
|
|
4888
|
+
(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
4889
|
+
ctx.clip();
|
|
4890
|
+
// Fill the clipped region with a radial gradient from background colors
|
|
4891
|
+
const portalColor = (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
4892
|
+
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
4893
|
+
portalGrad.addColorStop(0, portalColor);
|
|
4894
|
+
portalGrad.addColorStop(1, bgEnd);
|
|
4895
|
+
ctx.globalAlpha = portalAlpha;
|
|
4896
|
+
ctx.fillStyle = portalGrad;
|
|
4897
|
+
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
4898
|
+
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
4899
|
+
if (rng() < 0.5) {
|
|
4900
|
+
const dotCount = 3 + Math.floor(rng() * 5);
|
|
4901
|
+
ctx.globalAlpha = portalAlpha * 0.3;
|
|
4902
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
4903
|
+
for(let d = 0; d < dotCount; d++){
|
|
4904
|
+
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
4905
|
+
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
4906
|
+
const dr = (1 + rng() * 3) * scaleFactor;
|
|
4907
|
+
ctx.beginPath();
|
|
4908
|
+
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
4909
|
+
ctx.fill();
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
ctx.restore();
|
|
4913
|
+
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
4914
|
+
ctx.save();
|
|
4915
|
+
ctx.translate(portalX, portalY);
|
|
4916
|
+
ctx.rotate(portalRotation * Math.PI / 180);
|
|
4917
|
+
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
4918
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
4919
|
+
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
4920
|
+
ctx.beginPath();
|
|
4921
|
+
(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
4922
|
+
ctx.stroke();
|
|
4923
|
+
ctx.restore();
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4591
4926
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
4592
4927
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
4593
4928
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
@@ -4799,6 +5134,176 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4799
5134
|
ctx.restore();
|
|
4800
5135
|
ctx.globalCompositeOperation = "source-over";
|
|
4801
5136
|
}
|
|
5137
|
+
// 10d. Gradient map — map luminance through a two-color gradient
|
|
5138
|
+
// Uses dominant→accent as the dark→light ramp for a cohesive tonal look
|
|
5139
|
+
if (rng() < 0.35) {
|
|
5140
|
+
const gmDark = colorHierarchy.dominant;
|
|
5141
|
+
const gmLight = colorHierarchy.accent;
|
|
5142
|
+
ctx.globalAlpha = 0.06 + rng() * 0.06; // very subtle: 6-12%
|
|
5143
|
+
ctx.globalCompositeOperation = "color";
|
|
5144
|
+
// Paint a linear gradient from dark color (top) to light color (bottom)
|
|
5145
|
+
const gmGrad = ctx.createLinearGradient(0, 0, 0, height);
|
|
5146
|
+
gmGrad.addColorStop(0, gmDark);
|
|
5147
|
+
gmGrad.addColorStop(1, gmLight);
|
|
5148
|
+
ctx.fillStyle = gmGrad;
|
|
5149
|
+
ctx.fillRect(0, 0, width, height);
|
|
5150
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5151
|
+
}
|
|
5152
|
+
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5153
|
+
{
|
|
5154
|
+
ctx.save();
|
|
5155
|
+
ctx.globalAlpha = 1;
|
|
5156
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5157
|
+
const borderRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 314));
|
|
5158
|
+
const borderPad = Math.min(width, height) * 0.025;
|
|
5159
|
+
const borderColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
5160
|
+
const borderColorSolid = colorHierarchy.accent;
|
|
5161
|
+
const archName = archetype.name;
|
|
5162
|
+
if (archName.includes("geometric") || archName.includes("op-art") || archName.includes("shattered")) {
|
|
5163
|
+
// Clean ruled lines with corner ornaments
|
|
5164
|
+
ctx.strokeStyle = borderColor;
|
|
5165
|
+
ctx.lineWidth = Math.max(1, 1.5 * scaleFactor);
|
|
5166
|
+
ctx.globalAlpha = 0.18 + borderRng() * 0.1;
|
|
5167
|
+
// Outer rule
|
|
5168
|
+
ctx.strokeRect(borderPad, borderPad, width - borderPad * 2, height - borderPad * 2);
|
|
5169
|
+
// Inner rule (thinner, offset)
|
|
5170
|
+
const innerPad = borderPad * 1.8;
|
|
5171
|
+
ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
|
|
5172
|
+
ctx.globalAlpha *= 0.7;
|
|
5173
|
+
ctx.strokeRect(innerPad, innerPad, width - innerPad * 2, height - innerPad * 2);
|
|
5174
|
+
// Corner ornaments — small squares at each corner
|
|
5175
|
+
const ornSize = borderPad * 0.6;
|
|
5176
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(borderColorSolid, 0.12);
|
|
5177
|
+
const corners = [
|
|
5178
|
+
[
|
|
5179
|
+
borderPad,
|
|
5180
|
+
borderPad
|
|
5181
|
+
],
|
|
5182
|
+
[
|
|
5183
|
+
width - borderPad - ornSize,
|
|
5184
|
+
borderPad
|
|
5185
|
+
],
|
|
5186
|
+
[
|
|
5187
|
+
borderPad,
|
|
5188
|
+
height - borderPad - ornSize
|
|
5189
|
+
],
|
|
5190
|
+
[
|
|
5191
|
+
width - borderPad - ornSize,
|
|
5192
|
+
height - borderPad - ornSize
|
|
5193
|
+
]
|
|
5194
|
+
];
|
|
5195
|
+
for (const [cx2, cy2] of corners){
|
|
5196
|
+
ctx.fillRect(cx2, cy2, ornSize, ornSize);
|
|
5197
|
+
// Diagonal cross inside ornament
|
|
5198
|
+
ctx.beginPath();
|
|
5199
|
+
ctx.moveTo(cx2, cy2);
|
|
5200
|
+
ctx.lineTo(cx2 + ornSize, cy2 + ornSize);
|
|
5201
|
+
ctx.moveTo(cx2 + ornSize, cy2);
|
|
5202
|
+
ctx.lineTo(cx2, cy2 + ornSize);
|
|
5203
|
+
ctx.stroke();
|
|
5204
|
+
}
|
|
5205
|
+
} else if (archName.includes("botanical") || archName.includes("organic") || archName.includes("watercolor")) {
|
|
5206
|
+
// Vine tendrils — organic curving lines along edges
|
|
5207
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
5208
|
+
ctx.lineWidth = Math.max(0.8, 1.2 * scaleFactor);
|
|
5209
|
+
ctx.globalAlpha = 0.12 + borderRng() * 0.08;
|
|
5210
|
+
ctx.lineCap = "round";
|
|
5211
|
+
const tendrilCount = 8 + Math.floor(borderRng() * 8);
|
|
5212
|
+
for(let t = 0; t < tendrilCount; t++){
|
|
5213
|
+
// Start from a random edge point
|
|
5214
|
+
const edge = Math.floor(borderRng() * 4);
|
|
5215
|
+
let tx, ty;
|
|
5216
|
+
if (edge === 0) {
|
|
5217
|
+
tx = borderRng() * width;
|
|
5218
|
+
ty = borderPad;
|
|
5219
|
+
} else if (edge === 1) {
|
|
5220
|
+
tx = borderRng() * width;
|
|
5221
|
+
ty = height - borderPad;
|
|
5222
|
+
} else if (edge === 2) {
|
|
5223
|
+
tx = borderPad;
|
|
5224
|
+
ty = borderRng() * height;
|
|
5225
|
+
} else {
|
|
5226
|
+
tx = width - borderPad;
|
|
5227
|
+
ty = borderRng() * height;
|
|
5228
|
+
}
|
|
5229
|
+
ctx.beginPath();
|
|
5230
|
+
ctx.moveTo(tx, ty);
|
|
5231
|
+
const segs = 3 + Math.floor(borderRng() * 4);
|
|
5232
|
+
for(let s = 0; s < segs; s++){
|
|
5233
|
+
const inward = borderPad * (1 + borderRng() * 2);
|
|
5234
|
+
// Curl inward from edge
|
|
5235
|
+
const cpx2 = tx + (borderRng() - 0.5) * borderPad * 4;
|
|
5236
|
+
const cpy2 = ty + (edge < 2 ? edge === 0 ? inward : -inward : 0);
|
|
5237
|
+
const cpx3 = tx + (edge >= 2 ? edge === 2 ? inward : -inward : (borderRng() - 0.5) * borderPad * 3);
|
|
5238
|
+
const cpy3 = ty + (borderRng() - 0.5) * borderPad * 3;
|
|
5239
|
+
tx = cpx3;
|
|
5240
|
+
ty = cpy3;
|
|
5241
|
+
ctx.quadraticCurveTo(cpx2, cpy2, tx, ty);
|
|
5242
|
+
}
|
|
5243
|
+
ctx.stroke();
|
|
5244
|
+
// Small leaf/dot at tendril end
|
|
5245
|
+
if (borderRng() < 0.6) {
|
|
5246
|
+
ctx.beginPath();
|
|
5247
|
+
ctx.arc(tx, ty, borderPad * (0.15 + borderRng() * 0.2), 0, Math.PI * 2);
|
|
5248
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.08);
|
|
5249
|
+
ctx.fill();
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
} else if (archName.includes("celestial") || archName.includes("cosmic") || archName.includes("neon")) {
|
|
5253
|
+
// Star-studded arcs along edges
|
|
5254
|
+
ctx.globalAlpha = 0.1 + borderRng() * 0.08;
|
|
5255
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
5256
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.12);
|
|
5257
|
+
ctx.lineWidth = Math.max(0.5, 0.7 * scaleFactor);
|
|
5258
|
+
// Subtle arc along top and bottom
|
|
5259
|
+
ctx.beginPath();
|
|
5260
|
+
ctx.arc(cx, -height * 0.3, height * 0.6, 0.3, Math.PI - 0.3);
|
|
5261
|
+
ctx.stroke();
|
|
5262
|
+
ctx.beginPath();
|
|
5263
|
+
ctx.arc(cx, height * 1.3, height * 0.6, Math.PI + 0.3, -0.3);
|
|
5264
|
+
ctx.stroke();
|
|
5265
|
+
// Scatter small stars along the border region
|
|
5266
|
+
const starCount = 15 + Math.floor(borderRng() * 15);
|
|
5267
|
+
for(let s = 0; s < starCount; s++){
|
|
5268
|
+
const edge = Math.floor(borderRng() * 4);
|
|
5269
|
+
let sx, sy;
|
|
5270
|
+
if (edge === 0) {
|
|
5271
|
+
sx = borderRng() * width;
|
|
5272
|
+
sy = borderPad * (0.5 + borderRng());
|
|
5273
|
+
} else if (edge === 1) {
|
|
5274
|
+
sx = borderRng() * width;
|
|
5275
|
+
sy = height - borderPad * (0.5 + borderRng());
|
|
5276
|
+
} else if (edge === 2) {
|
|
5277
|
+
sx = borderPad * (0.5 + borderRng());
|
|
5278
|
+
sy = borderRng() * height;
|
|
5279
|
+
} else {
|
|
5280
|
+
sx = width - borderPad * (0.5 + borderRng());
|
|
5281
|
+
sy = borderRng() * height;
|
|
5282
|
+
}
|
|
5283
|
+
const starR = (1 + borderRng() * 2.5) * scaleFactor;
|
|
5284
|
+
// 4-point star
|
|
5285
|
+
ctx.beginPath();
|
|
5286
|
+
for(let p = 0; p < 8; p++){
|
|
5287
|
+
const a = p / 8 * Math.PI * 2;
|
|
5288
|
+
const r = p % 2 === 0 ? starR : starR * 0.4;
|
|
5289
|
+
const px2 = sx + Math.cos(a) * r;
|
|
5290
|
+
const py2 = sy + Math.sin(a) * r;
|
|
5291
|
+
if (p === 0) ctx.moveTo(px2, py2);
|
|
5292
|
+
else ctx.lineTo(px2, py2);
|
|
5293
|
+
}
|
|
5294
|
+
ctx.closePath();
|
|
5295
|
+
ctx.fill();
|
|
5296
|
+
}
|
|
5297
|
+
} else if (archName.includes("minimal") || archName.includes("monochrome") || archName.includes("stipple")) {
|
|
5298
|
+
// Thin single rule — understated elegance
|
|
5299
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
5300
|
+
ctx.lineWidth = Math.max(0.5, 0.6 * scaleFactor);
|
|
5301
|
+
ctx.globalAlpha = 0.1 + borderRng() * 0.06;
|
|
5302
|
+
ctx.strokeRect(borderPad * 1.5, borderPad * 1.5, width - borderPad * 3, height - borderPad * 3);
|
|
5303
|
+
}
|
|
5304
|
+
// Other archetypes: no border (intentional — not every image needs one)
|
|
5305
|
+
ctx.restore();
|
|
5306
|
+
}
|
|
4802
5307
|
// ── 11. Signature mark — unique geometric chop from hash prefix ──
|
|
4803
5308
|
{
|
|
4804
5309
|
const sigRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 42));
|