git-hash-art 0.9.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 +212 -14
- package/CHANGELOG.md +18 -0
- package/dist/browser.js +758 -23
- package/dist/browser.js.map +1 -1
- package/dist/main.js +758 -23
- package/dist/main.js.map +1 -1
- package/dist/module.js +758 -23
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/archetypes.ts +51 -4
- package/src/lib/canvas/colors.ts +70 -0
- package/src/lib/canvas/draw.ts +115 -6
- package/src/lib/canvas/shapes/affinity.ts +3 -3
- package/src/lib/render.ts +423 -17
- 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",
|
|
@@ -541,6 +721,19 @@ function $d016ad53434219a1$export$6d1620b367f86f7a(rng) {
|
|
|
541
721
|
intensity: intensity
|
|
542
722
|
};
|
|
543
723
|
}
|
|
724
|
+
function $d016ad53434219a1$export$1793a1bfbe4f6ff5(hex, degrees) {
|
|
725
|
+
const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
|
|
726
|
+
return $d016ad53434219a1$var$hslToHex((h + degrees + 360) % 360, s, l);
|
|
727
|
+
}
|
|
728
|
+
function $d016ad53434219a1$export$703ba40a4347f77a(base, layerRatio, hueShiftPerLayer) {
|
|
729
|
+
const shift = layerRatio * hueShiftPerLayer;
|
|
730
|
+
return {
|
|
731
|
+
dominant: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.dominant, shift),
|
|
732
|
+
secondary: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.secondary, shift * 0.7),
|
|
733
|
+
accent: $d016ad53434219a1$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5),
|
|
734
|
+
all: base.all.map((c)=>$d016ad53434219a1$export$1793a1bfbe4f6ff5(c, shift * 0.6))
|
|
735
|
+
};
|
|
736
|
+
}
|
|
544
737
|
|
|
545
738
|
|
|
546
739
|
|
|
@@ -1824,7 +2017,8 @@ const $c3de8257a8baa3b0$var$RENDER_STYLES = [
|
|
|
1824
2017
|
"noise-grain",
|
|
1825
2018
|
"wood-grain",
|
|
1826
2019
|
"marble-vein",
|
|
1827
|
-
"fabric-weave"
|
|
2020
|
+
"fabric-weave",
|
|
2021
|
+
"hand-drawn"
|
|
1828
2022
|
];
|
|
1829
2023
|
function $c3de8257a8baa3b0$export$9fd4e64b2acd410e(rng) {
|
|
1830
2024
|
return $c3de8257a8baa3b0$var$RENDER_STYLES[Math.floor(rng() * $c3de8257a8baa3b0$var$RENDER_STYLES.length)];
|
|
@@ -1920,6 +2114,23 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1920
2114
|
ctx.fill();
|
|
1921
2115
|
ctx.fillStyle = origFill;
|
|
1922
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
|
+
}
|
|
1923
2134
|
ctx.globalAlpha = savedAlpha;
|
|
1924
2135
|
// Soft stroke on top — thinner than normal for delicacy
|
|
1925
2136
|
ctx.globalAlpha *= 0.25;
|
|
@@ -2205,6 +2416,50 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2205
2416
|
ctx.globalAlpha /= 0.3;
|
|
2206
2417
|
break;
|
|
2207
2418
|
}
|
|
2419
|
+
case "hand-drawn":
|
|
2420
|
+
{
|
|
2421
|
+
// Wobbly hand-drawn edge treatment — fill normally, then redraw
|
|
2422
|
+
// the outline with perturbed control points for a sketchy feel
|
|
2423
|
+
const savedAlphaHD = ctx.globalAlpha;
|
|
2424
|
+
ctx.globalAlpha = savedAlphaHD * 0.85;
|
|
2425
|
+
ctx.fill();
|
|
2426
|
+
ctx.globalAlpha = savedAlphaHD;
|
|
2427
|
+
// Draw 2-3 slightly offset wobbly strokes for a sketchy look
|
|
2428
|
+
const wobblePasses = 2 + (rng ? Math.floor(rng() * 2) : 0);
|
|
2429
|
+
ctx.lineWidth = strokeWidth * 0.8;
|
|
2430
|
+
for(let wp = 0; wp < wobblePasses; wp++){
|
|
2431
|
+
ctx.globalAlpha = savedAlphaHD * (0.4 - wp * 0.1);
|
|
2432
|
+
ctx.save();
|
|
2433
|
+
// Slight random offset per pass
|
|
2434
|
+
const wobbleX = rng ? (rng() - 0.5) * size * 0.02 : 0;
|
|
2435
|
+
const wobbleY = rng ? (rng() - 0.5) * size * 0.02 : 0;
|
|
2436
|
+
ctx.translate(wobbleX, wobbleY);
|
|
2437
|
+
// Slightly different scale per pass for edge variation
|
|
2438
|
+
const wobbleScale = 1 + (rng ? (rng() - 0.5) * 0.03 : 0);
|
|
2439
|
+
ctx.scale(wobbleScale, wobbleScale);
|
|
2440
|
+
ctx.stroke();
|
|
2441
|
+
ctx.restore();
|
|
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
|
+
}
|
|
2460
|
+
ctx.globalAlpha = savedAlphaHD;
|
|
2461
|
+
break;
|
|
2462
|
+
}
|
|
2208
2463
|
case "fill-and-stroke":
|
|
2209
2464
|
default:
|
|
2210
2465
|
ctx.fill();
|
|
@@ -2213,12 +2468,20 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2213
2468
|
}
|
|
2214
2469
|
}
|
|
2215
2470
|
function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
2216
|
-
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;
|
|
2217
2472
|
ctx.save();
|
|
2218
2473
|
ctx.translate(x, y);
|
|
2219
2474
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2220
|
-
//
|
|
2221
|
-
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)
|
|
2222
2485
|
ctx.shadowBlur = glowRadius;
|
|
2223
2486
|
ctx.shadowColor = glowColor || fillColor;
|
|
2224
2487
|
ctx.shadowOffsetX = 0;
|
|
@@ -2240,8 +2503,29 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2240
2503
|
});
|
|
2241
2504
|
$c3de8257a8baa3b0$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
2242
2505
|
}
|
|
2243
|
-
// Reset shadow so patterns aren't double-
|
|
2244
|
-
|
|
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
|
+
}
|
|
2245
2529
|
// Layer additional patterns if specified
|
|
2246
2530
|
if (patterns.length > 0) (0, $e4b03e131ed2a289$export$da2372f11bc66b3f).layerPatterns(ctx, patterns, {
|
|
2247
2531
|
baseSize: size,
|
|
@@ -2340,7 +2624,8 @@ const $e73976f898150d4d$export$4343b39fe47bd82c = {
|
|
|
2340
2624
|
bestStyles: [
|
|
2341
2625
|
"fill-only",
|
|
2342
2626
|
"watercolor",
|
|
2343
|
-
"fill-and-stroke"
|
|
2627
|
+
"fill-and-stroke",
|
|
2628
|
+
"hand-drawn"
|
|
2344
2629
|
]
|
|
2345
2630
|
},
|
|
2346
2631
|
square: {
|
|
@@ -2377,7 +2662,8 @@ const $e73976f898150d4d$export$4343b39fe47bd82c = {
|
|
|
2377
2662
|
bestStyles: [
|
|
2378
2663
|
"fill-and-stroke",
|
|
2379
2664
|
"fill-only",
|
|
2380
|
-
"watercolor"
|
|
2665
|
+
"watercolor",
|
|
2666
|
+
"hand-drawn"
|
|
2381
2667
|
]
|
|
2382
2668
|
},
|
|
2383
2669
|
hexagon: {
|
|
@@ -2757,7 +3043,8 @@ const $e73976f898150d4d$export$4343b39fe47bd82c = {
|
|
|
2757
3043
|
bestStyles: [
|
|
2758
3044
|
"fill-only",
|
|
2759
3045
|
"watercolor",
|
|
2760
|
-
"fill-and-stroke"
|
|
3046
|
+
"fill-and-stroke",
|
|
3047
|
+
"hand-drawn"
|
|
2761
3048
|
]
|
|
2762
3049
|
},
|
|
2763
3050
|
ngon: {
|
|
@@ -3637,8 +3924,51 @@ const $f89bc858f7202849$var$ARCHETYPES = [
|
|
|
3637
3924
|
invertForeground: false
|
|
3638
3925
|
}
|
|
3639
3926
|
];
|
|
3927
|
+
/**
|
|
3928
|
+
* Linearly interpolate between two archetype numeric parameters.
|
|
3929
|
+
*/ function $f89bc858f7202849$var$lerpNum(a, b, t) {
|
|
3930
|
+
return a + (b - a) * t;
|
|
3931
|
+
}
|
|
3932
|
+
/**
|
|
3933
|
+
* Blend two archetypes by interpolating their numeric parameters
|
|
3934
|
+
* and merging their style arrays.
|
|
3935
|
+
*/ function $f89bc858f7202849$var$blendArchetypes(a, b, t) {
|
|
3936
|
+
// Merge preferred styles — unique union, primary archetype first
|
|
3937
|
+
const mergedStyles = [
|
|
3938
|
+
...new Set([
|
|
3939
|
+
...a.preferredStyles,
|
|
3940
|
+
...b.preferredStyles
|
|
3941
|
+
])
|
|
3942
|
+
];
|
|
3943
|
+
return {
|
|
3944
|
+
name: `${a.name}+${b.name}`,
|
|
3945
|
+
gridSize: Math.round($f89bc858f7202849$var$lerpNum(a.gridSize, b.gridSize, t)),
|
|
3946
|
+
layers: Math.round($f89bc858f7202849$var$lerpNum(a.layers, b.layers, t)),
|
|
3947
|
+
baseOpacity: $f89bc858f7202849$var$lerpNum(a.baseOpacity, b.baseOpacity, t),
|
|
3948
|
+
opacityReduction: $f89bc858f7202849$var$lerpNum(a.opacityReduction, b.opacityReduction, t),
|
|
3949
|
+
minShapeSize: Math.round($f89bc858f7202849$var$lerpNum(a.minShapeSize, b.minShapeSize, t)),
|
|
3950
|
+
maxShapeSize: Math.round($f89bc858f7202849$var$lerpNum(a.maxShapeSize, b.maxShapeSize, t)),
|
|
3951
|
+
backgroundStyle: t < 0.5 ? a.backgroundStyle : b.backgroundStyle,
|
|
3952
|
+
paletteMode: t < 0.5 ? a.paletteMode : b.paletteMode,
|
|
3953
|
+
preferredStyles: mergedStyles,
|
|
3954
|
+
flowLineMultiplier: $f89bc858f7202849$var$lerpNum(a.flowLineMultiplier, b.flowLineMultiplier, t),
|
|
3955
|
+
heroShape: t < 0.5 ? a.heroShape : b.heroShape,
|
|
3956
|
+
glowMultiplier: $f89bc858f7202849$var$lerpNum(a.glowMultiplier, b.glowMultiplier, t),
|
|
3957
|
+
sizePower: $f89bc858f7202849$var$lerpNum(a.sizePower, b.sizePower, t),
|
|
3958
|
+
invertForeground: t < 0.5 ? a.invertForeground : b.invertForeground
|
|
3959
|
+
};
|
|
3960
|
+
}
|
|
3640
3961
|
function $f89bc858f7202849$export$f1142fd7da4d6590(rng) {
|
|
3641
|
-
|
|
3962
|
+
const primary = $f89bc858f7202849$var$ARCHETYPES[Math.floor(rng() * $f89bc858f7202849$var$ARCHETYPES.length)];
|
|
3963
|
+
// ~15% chance of blending with a second archetype
|
|
3964
|
+
if (rng() < 0.15) {
|
|
3965
|
+
const secondary = $f89bc858f7202849$var$ARCHETYPES[Math.floor(rng() * $f89bc858f7202849$var$ARCHETYPES.length)];
|
|
3966
|
+
if (secondary.name !== primary.name) {
|
|
3967
|
+
const blendT = 0.25 + rng() * 0.25; // 25-50% blend toward secondary
|
|
3968
|
+
return $f89bc858f7202849$var$blendArchetypes(primary, secondary, blendT);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
return primary;
|
|
3642
3972
|
}
|
|
3643
3973
|
|
|
3644
3974
|
|
|
@@ -3659,7 +3989,8 @@ const $4f72c5a314eddf25$var$COMPOSITION_MODES = [
|
|
|
3659
3989
|
"flow-field",
|
|
3660
3990
|
"spiral",
|
|
3661
3991
|
"grid-subdivision",
|
|
3662
|
-
"clustered"
|
|
3992
|
+
"clustered",
|
|
3993
|
+
"golden-spiral"
|
|
3663
3994
|
];
|
|
3664
3995
|
// ── Helper: get position based on composition mode ──────────────────
|
|
3665
3996
|
function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
@@ -3717,6 +4048,21 @@ function $4f72c5a314eddf25$var$getCompositionPosition(mode, rng, width, height,
|
|
|
3717
4048
|
x: rng() * width,
|
|
3718
4049
|
y: rng() * height
|
|
3719
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
|
+
}
|
|
3720
4066
|
}
|
|
3721
4067
|
}
|
|
3722
4068
|
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
@@ -3974,6 +4320,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3974
4320
|
const colorGrade = (0, $d016ad53434219a1$export$6d1620b367f86f7a)(rng);
|
|
3975
4321
|
// ── 0e. Light direction — consistent shadow angle ──────────────
|
|
3976
4322
|
const lightAngle = rng() * Math.PI * 2;
|
|
4323
|
+
// ── 0f. Palette evolution — hue drift direction across layers ──
|
|
4324
|
+
const paletteHueShift = (rng() - 0.5) * 40; // -20° to +20° total drift
|
|
3977
4325
|
const scaleFactor = Math.min(width, height) / 1024;
|
|
3978
4326
|
const adjustedMinSize = minShapeSize * scaleFactor;
|
|
3979
4327
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
@@ -4148,11 +4496,59 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4148
4496
|
ry + (nearest.y - ry) * pull
|
|
4149
4497
|
];
|
|
4150
4498
|
}
|
|
4151
|
-
// ──
|
|
4499
|
+
// ── 3b. Void zone decoration — intentional negative space ────
|
|
4500
|
+
for (const zone of voidZones){
|
|
4501
|
+
// Subtle halo ring around void zones
|
|
4502
|
+
ctx.globalAlpha = 0.04 + rng() * 0.04;
|
|
4503
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
4504
|
+
ctx.lineWidth = 1.5 * scaleFactor;
|
|
4505
|
+
ctx.beginPath();
|
|
4506
|
+
ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2);
|
|
4507
|
+
ctx.stroke();
|
|
4508
|
+
// ~50% chance: scatter tiny dots inside the void
|
|
4509
|
+
if (rng() < 0.5) {
|
|
4510
|
+
const dotCount = 3 + Math.floor(rng() * 6);
|
|
4511
|
+
ctx.globalAlpha = 0.06 + rng() * 0.04;
|
|
4512
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
4513
|
+
for(let d = 0; d < dotCount; d++){
|
|
4514
|
+
const angle = rng() * Math.PI * 2;
|
|
4515
|
+
const dist = rng() * zone.radius * 0.7;
|
|
4516
|
+
const dotR = (1 + rng() * 3) * scaleFactor;
|
|
4517
|
+
ctx.beginPath();
|
|
4518
|
+
ctx.arc(zone.x + Math.cos(angle) * dist, zone.y + Math.sin(angle) * dist, dotR, 0, Math.PI * 2);
|
|
4519
|
+
ctx.fill();
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
// ~30% chance: thin concentric ring inside
|
|
4523
|
+
if (rng() < 0.3) {
|
|
4524
|
+
ctx.globalAlpha = 0.03 + rng() * 0.03;
|
|
4525
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4526
|
+
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4527
|
+
const innerR = zone.radius * (0.4 + rng() * 0.3);
|
|
4528
|
+
ctx.beginPath();
|
|
4529
|
+
ctx.arc(zone.x, zone.y, innerR, 0, Math.PI * 2);
|
|
4530
|
+
ctx.stroke();
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
ctx.globalAlpha = 1;
|
|
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);
|
|
4152
4539
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
4153
|
-
const fieldFreq =
|
|
4540
|
+
const fieldFreq = 1.5 + rng() * 2.5; // noise sampling frequency
|
|
4154
4541
|
function flowAngle(x, y) {
|
|
4155
|
-
|
|
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;
|
|
4156
4552
|
}
|
|
4157
4553
|
// Track all placed shapes for density checks and connecting curves
|
|
4158
4554
|
const shapePositions = [];
|
|
@@ -4186,7 +4582,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4186
4582
|
glowColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
4187
4583
|
gradientFillEnd: (0, $d016ad53434219a1$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
4188
4584
|
renderStyle: heroStyle,
|
|
4189
|
-
rng: rng
|
|
4585
|
+
rng: rng,
|
|
4586
|
+
lightAngle: lightAngle,
|
|
4587
|
+
scaleFactor: scaleFactor
|
|
4190
4588
|
});
|
|
4191
4589
|
heroCenter = {
|
|
4192
4590
|
x: heroFocal.x,
|
|
@@ -4220,6 +4618,18 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4220
4618
|
const dofFactor = 1 - layerRatio * 0.5; // 1.0 for front layer, 0.5 for back
|
|
4221
4619
|
const dofStrokeScale = 0.4 + dofFactor * 0.6; // strokes thin out with depth
|
|
4222
4620
|
const dofContrastReduction = layerRatio * 0.2; // colors fade toward bg
|
|
4621
|
+
// Color palette evolution — hue-rotate the hierarchy per layer
|
|
4622
|
+
const layerHierarchy = (0, $d016ad53434219a1$export$703ba40a4347f77a)(colorHierarchy, layerRatio, paletteHueShift);
|
|
4623
|
+
// Focal depth: shapes near focal points get more detail
|
|
4624
|
+
const focalDetailBoost = (px, py)=>{
|
|
4625
|
+
let minFocalDist = Infinity;
|
|
4626
|
+
for (const fp of focalPoints){
|
|
4627
|
+
const d = Math.hypot(px - fp.x, py - fp.y);
|
|
4628
|
+
if (d < minFocalDist) minFocalDist = d;
|
|
4629
|
+
}
|
|
4630
|
+
const maxDist = Math.hypot(width, height) * 0.5;
|
|
4631
|
+
return Math.max(0, 1 - minFocalDist / maxDist); // 1.0 at focal, 0.0 at edges
|
|
4632
|
+
};
|
|
4223
4633
|
for(let i = 0; i < numShapes; i++){
|
|
4224
4634
|
// Position from composition mode, then focal bias
|
|
4225
4635
|
const rawPos = $4f72c5a314eddf25$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
@@ -4233,7 +4643,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4233
4643
|
}
|
|
4234
4644
|
// Power distribution for size — archetype controls the curve
|
|
4235
4645
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
4236
|
-
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
4646
|
+
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale * noiseSizeModulation(x, y);
|
|
4237
4647
|
// Size fraction for affinity-aware shape selection
|
|
4238
4648
|
const sizeFraction = size / adjustedMaxSize;
|
|
4239
4649
|
// Palette-driven shape selection (replaces naive pickShape)
|
|
@@ -4250,9 +4660,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4250
4660
|
rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
|
|
4251
4661
|
}
|
|
4252
4662
|
}
|
|
4253
|
-
// Positional color from hierarchy + jitter
|
|
4254
|
-
let fillBase = $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height,
|
|
4255
|
-
const strokeBase = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(
|
|
4663
|
+
// Positional color from hierarchy + jitter (using evolved layer palette)
|
|
4664
|
+
let fillBase = $4f72c5a314eddf25$var$getPositionalColor(x, y, width, height, layerHierarchy, rng);
|
|
4665
|
+
const strokeBase = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(layerHierarchy, rng);
|
|
4256
4666
|
// Desaturate colors on later layers for depth
|
|
4257
4667
|
if (atmosphericDesat > 0) fillBase = (0, $d016ad53434219a1$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
4258
4668
|
// Temperature contrast: shift foreground shapes opposite to background
|
|
@@ -4336,7 +4746,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4336
4746
|
glowColor: hasGlow ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
4337
4747
|
gradientFillEnd: gradientEnd,
|
|
4338
4748
|
renderStyle: finalRenderStyle,
|
|
4339
|
-
rng: rng
|
|
4749
|
+
rng: rng,
|
|
4750
|
+
lightAngle: lightAngle,
|
|
4751
|
+
scaleFactor: scaleFactor
|
|
4340
4752
|
};
|
|
4341
4753
|
if (shouldMirror) (0, $c3de8257a8baa3b0$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4342
4754
|
...shapeConfig,
|
|
@@ -4344,6 +4756,25 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4344
4756
|
mirrorGap: size * (0.1 + rng() * 0.3)
|
|
4345
4757
|
});
|
|
4346
4758
|
else (0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
|
|
4759
|
+
// ── Glazing — luminous multi-pass transparency on ~20% of shapes ──
|
|
4760
|
+
if (rng() < 0.2 && size > adjustedMinSize * 2) {
|
|
4761
|
+
const glazePasses = 2 + Math.floor(rng() * 2);
|
|
4762
|
+
for(let g = 0; g < glazePasses; g++){
|
|
4763
|
+
const glazeScale = 1 - (g + 1) * 0.12; // progressively smaller
|
|
4764
|
+
const glazeAlpha = 0.08 + g * 0.04; // progressively more opaque toward center
|
|
4765
|
+
ctx.globalAlpha = glazeAlpha;
|
|
4766
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, {
|
|
4767
|
+
fillColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.15 + g * 0.1),
|
|
4768
|
+
strokeColor: "rgba(0,0,0,0)",
|
|
4769
|
+
strokeWidth: 0,
|
|
4770
|
+
size: size * glazeScale,
|
|
4771
|
+
rotation: rotation,
|
|
4772
|
+
proportionType: "GOLDEN_RATIO",
|
|
4773
|
+
renderStyle: "fill-only",
|
|
4774
|
+
rng: rng
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
}
|
|
4347
4778
|
shapePositions.push({
|
|
4348
4779
|
x: finalX,
|
|
4349
4780
|
y: finalY,
|
|
@@ -4381,7 +4812,10 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4381
4812
|
}
|
|
4382
4813
|
}
|
|
4383
4814
|
// ── 5d. Recursive nesting ──────────────────────────────────
|
|
4384
|
-
|
|
4815
|
+
// Focal depth: shapes near focal points get more detail
|
|
4816
|
+
const focalProximity = focalDetailBoost(finalX, finalY);
|
|
4817
|
+
const nestingChance = 0.15 + focalProximity * 0.15; // 15-30% near focal
|
|
4818
|
+
if (size > adjustedMaxSize * 0.4 && rng() < nestingChance) {
|
|
4385
4819
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
4386
4820
|
for(let n = 0; n < innerCount; n++){
|
|
4387
4821
|
// Pick inner shape from palette affinities
|
|
@@ -4406,7 +4840,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4406
4840
|
}
|
|
4407
4841
|
}
|
|
4408
4842
|
// ── 5e. Shape constellations — pre-composed groups ─────────
|
|
4409
|
-
|
|
4843
|
+
const constellationChance = 0.12 + focalProximity * 0.1; // 12-22% near focal
|
|
4844
|
+
if (size > adjustedMaxSize * 0.35 && rng() < constellationChance) {
|
|
4410
4845
|
const constellation = $4f72c5a314eddf25$var$CONSTELLATIONS[Math.floor(rng() * $4f72c5a314eddf25$var$CONSTELLATIONS.length)];
|
|
4411
4846
|
const members = constellation.build(rng, size);
|
|
4412
4847
|
const groupRotation = rng() * Math.PI * 2;
|
|
@@ -4444,6 +4879,64 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4444
4879
|
}
|
|
4445
4880
|
// Reset blend mode for post-processing passes
|
|
4446
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
|
+
}
|
|
4447
4940
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
4448
4941
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
4449
4942
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
@@ -4510,7 +5003,41 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4510
5003
|
prevY = fy;
|
|
4511
5004
|
}
|
|
4512
5005
|
}
|
|
4513
|
-
// ── 6b.
|
|
5006
|
+
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
5007
|
+
const energyArchetypes = [
|
|
5008
|
+
"dense-chaotic",
|
|
5009
|
+
"cosmic",
|
|
5010
|
+
"neon-glow",
|
|
5011
|
+
"bold-graphic"
|
|
5012
|
+
];
|
|
5013
|
+
const hasEnergyLines = energyArchetypes.some((a)=>archetype.name.includes(a)) || rng() < 0.25;
|
|
5014
|
+
if (hasEnergyLines && shapePositions.length > 0) {
|
|
5015
|
+
const energyCount = 5 + Math.floor(rng() * 10);
|
|
5016
|
+
ctx.lineCap = "round";
|
|
5017
|
+
for(let e = 0; e < energyCount; e++){
|
|
5018
|
+
// Pick a random shape to radiate from
|
|
5019
|
+
const source = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5020
|
+
const burstCount = 2 + Math.floor(rng() * 4);
|
|
5021
|
+
const baseAngle = flowAngle(source.x, source.y);
|
|
5022
|
+
for(let b = 0; b < burstCount; b++){
|
|
5023
|
+
const angle = baseAngle + (rng() - 0.5) * 1.2;
|
|
5024
|
+
const lineLen = (source.size * 0.3 + rng() * source.size * 0.5) * scaleFactor * 0.3;
|
|
5025
|
+
const startDist = source.size * 0.5;
|
|
5026
|
+
const sx = source.x + Math.cos(angle) * startDist;
|
|
5027
|
+
const sy = source.y + Math.sin(angle) * startDist;
|
|
5028
|
+
const ex = sx + Math.cos(angle) * lineLen;
|
|
5029
|
+
const ey = sy + Math.sin(angle) * lineLen;
|
|
5030
|
+
ctx.globalAlpha = 0.04 + rng() * 0.06;
|
|
5031
|
+
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
5032
|
+
ctx.lineWidth = (0.5 + rng() * 1.5) * scaleFactor;
|
|
5033
|
+
ctx.beginPath();
|
|
5034
|
+
ctx.moveTo(sx, sy);
|
|
5035
|
+
ctx.lineTo(ex, ey);
|
|
5036
|
+
ctx.stroke();
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
4514
5041
|
if (symmetryMode !== "none") {
|
|
4515
5042
|
const canvas = ctx.canvas;
|
|
4516
5043
|
ctx.save();
|
|
@@ -4621,6 +5148,214 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4621
5148
|
ctx.restore();
|
|
4622
5149
|
ctx.globalCompositeOperation = "source-over";
|
|
4623
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
|
+
}
|
|
5321
|
+
// ── 11. Signature mark — unique geometric chop from hash prefix ──
|
|
5322
|
+
{
|
|
5323
|
+
const sigRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 42));
|
|
5324
|
+
const sigSize = Math.min(width, height) * 0.025;
|
|
5325
|
+
// Bottom-right corner with padding
|
|
5326
|
+
const sigX = width - sigSize * 2.5;
|
|
5327
|
+
const sigY = height - sigSize * 2.5;
|
|
5328
|
+
const sigSegments = 4 + Math.floor(sigRng() * 4); // 4-7 segments
|
|
5329
|
+
const sigColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.accent, 0.15);
|
|
5330
|
+
ctx.save();
|
|
5331
|
+
ctx.globalAlpha = 0.12 + sigRng() * 0.08;
|
|
5332
|
+
ctx.translate(sigX, sigY);
|
|
5333
|
+
ctx.strokeStyle = sigColor;
|
|
5334
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.06);
|
|
5335
|
+
ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
|
|
5336
|
+
// Outer ring
|
|
5337
|
+
ctx.beginPath();
|
|
5338
|
+
ctx.arc(0, 0, sigSize, 0, Math.PI * 2);
|
|
5339
|
+
ctx.stroke();
|
|
5340
|
+
ctx.fill();
|
|
5341
|
+
// Inner geometric pattern — unique per hash
|
|
5342
|
+
ctx.beginPath();
|
|
5343
|
+
for(let s = 0; s < sigSegments; s++){
|
|
5344
|
+
const angle1 = sigRng() * Math.PI * 2;
|
|
5345
|
+
const angle2 = sigRng() * Math.PI * 2;
|
|
5346
|
+
const r1 = sigSize * (0.2 + sigRng() * 0.6);
|
|
5347
|
+
const r2 = sigSize * (0.2 + sigRng() * 0.6);
|
|
5348
|
+
ctx.moveTo(Math.cos(angle1) * r1, Math.sin(angle1) * r1);
|
|
5349
|
+
ctx.lineTo(Math.cos(angle2) * r2, Math.sin(angle2) * r2);
|
|
5350
|
+
}
|
|
5351
|
+
ctx.stroke();
|
|
5352
|
+
// Center dot
|
|
5353
|
+
ctx.beginPath();
|
|
5354
|
+
ctx.arc(0, 0, sigSize * 0.12, 0, Math.PI * 2);
|
|
5355
|
+
ctx.fillStyle = sigColor;
|
|
5356
|
+
ctx.fill();
|
|
5357
|
+
ctx.restore();
|
|
5358
|
+
}
|
|
4624
5359
|
ctx.globalAlpha = 1;
|
|
4625
5360
|
}
|
|
4626
5361
|
|