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/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",
|
|
@@ -527,6 +707,19 @@ function $9d614e7d77fc2947$export$6d1620b367f86f7a(rng) {
|
|
|
527
707
|
intensity: intensity
|
|
528
708
|
};
|
|
529
709
|
}
|
|
710
|
+
function $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(hex, degrees) {
|
|
711
|
+
const [h, s, l] = $9d614e7d77fc2947$var$hexToHsl(hex);
|
|
712
|
+
return $9d614e7d77fc2947$var$hslToHex((h + degrees + 360) % 360, s, l);
|
|
713
|
+
}
|
|
714
|
+
function $9d614e7d77fc2947$export$703ba40a4347f77a(base, layerRatio, hueShiftPerLayer) {
|
|
715
|
+
const shift = layerRatio * hueShiftPerLayer;
|
|
716
|
+
return {
|
|
717
|
+
dominant: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.dominant, shift),
|
|
718
|
+
secondary: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.secondary, shift * 0.7),
|
|
719
|
+
accent: $9d614e7d77fc2947$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5),
|
|
720
|
+
all: base.all.map((c)=>$9d614e7d77fc2947$export$1793a1bfbe4f6ff5(c, shift * 0.6))
|
|
721
|
+
};
|
|
722
|
+
}
|
|
530
723
|
|
|
531
724
|
|
|
532
725
|
|
|
@@ -1810,7 +2003,8 @@ const $9beb8f41637c29fd$var$RENDER_STYLES = [
|
|
|
1810
2003
|
"noise-grain",
|
|
1811
2004
|
"wood-grain",
|
|
1812
2005
|
"marble-vein",
|
|
1813
|
-
"fabric-weave"
|
|
2006
|
+
"fabric-weave",
|
|
2007
|
+
"hand-drawn"
|
|
1814
2008
|
];
|
|
1815
2009
|
function $9beb8f41637c29fd$export$9fd4e64b2acd410e(rng) {
|
|
1816
2010
|
return $9beb8f41637c29fd$var$RENDER_STYLES[Math.floor(rng() * $9beb8f41637c29fd$var$RENDER_STYLES.length)];
|
|
@@ -1906,6 +2100,23 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1906
2100
|
ctx.fill();
|
|
1907
2101
|
ctx.fillStyle = origFill;
|
|
1908
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
|
+
}
|
|
1909
2120
|
ctx.globalAlpha = savedAlpha;
|
|
1910
2121
|
// Soft stroke on top — thinner than normal for delicacy
|
|
1911
2122
|
ctx.globalAlpha *= 0.25;
|
|
@@ -2191,6 +2402,50 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2191
2402
|
ctx.globalAlpha /= 0.3;
|
|
2192
2403
|
break;
|
|
2193
2404
|
}
|
|
2405
|
+
case "hand-drawn":
|
|
2406
|
+
{
|
|
2407
|
+
// Wobbly hand-drawn edge treatment — fill normally, then redraw
|
|
2408
|
+
// the outline with perturbed control points for a sketchy feel
|
|
2409
|
+
const savedAlphaHD = ctx.globalAlpha;
|
|
2410
|
+
ctx.globalAlpha = savedAlphaHD * 0.85;
|
|
2411
|
+
ctx.fill();
|
|
2412
|
+
ctx.globalAlpha = savedAlphaHD;
|
|
2413
|
+
// Draw 2-3 slightly offset wobbly strokes for a sketchy look
|
|
2414
|
+
const wobblePasses = 2 + (rng ? Math.floor(rng() * 2) : 0);
|
|
2415
|
+
ctx.lineWidth = strokeWidth * 0.8;
|
|
2416
|
+
for(let wp = 0; wp < wobblePasses; wp++){
|
|
2417
|
+
ctx.globalAlpha = savedAlphaHD * (0.4 - wp * 0.1);
|
|
2418
|
+
ctx.save();
|
|
2419
|
+
// Slight random offset per pass
|
|
2420
|
+
const wobbleX = rng ? (rng() - 0.5) * size * 0.02 : 0;
|
|
2421
|
+
const wobbleY = rng ? (rng() - 0.5) * size * 0.02 : 0;
|
|
2422
|
+
ctx.translate(wobbleX, wobbleY);
|
|
2423
|
+
// Slightly different scale per pass for edge variation
|
|
2424
|
+
const wobbleScale = 1 + (rng ? (rng() - 0.5) * 0.03 : 0);
|
|
2425
|
+
ctx.scale(wobbleScale, wobbleScale);
|
|
2426
|
+
ctx.stroke();
|
|
2427
|
+
ctx.restore();
|
|
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
|
+
}
|
|
2446
|
+
ctx.globalAlpha = savedAlphaHD;
|
|
2447
|
+
break;
|
|
2448
|
+
}
|
|
2194
2449
|
case "fill-and-stroke":
|
|
2195
2450
|
default:
|
|
2196
2451
|
ctx.fill();
|
|
@@ -2199,12 +2454,20 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2199
2454
|
}
|
|
2200
2455
|
}
|
|
2201
2456
|
function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
2202
|
-
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;
|
|
2203
2458
|
ctx.save();
|
|
2204
2459
|
ctx.translate(x, y);
|
|
2205
2460
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2206
|
-
//
|
|
2207
|
-
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)
|
|
2208
2471
|
ctx.shadowBlur = glowRadius;
|
|
2209
2472
|
ctx.shadowColor = glowColor || fillColor;
|
|
2210
2473
|
ctx.shadowOffsetX = 0;
|
|
@@ -2226,8 +2489,29 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2226
2489
|
});
|
|
2227
2490
|
$9beb8f41637c29fd$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
2228
2491
|
}
|
|
2229
|
-
// Reset shadow so patterns aren't double-
|
|
2230
|
-
|
|
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
|
+
}
|
|
2231
2515
|
// Layer additional patterns if specified
|
|
2232
2516
|
if (patterns.length > 0) (0, $461134e0b6ce0619$export$da2372f11bc66b3f).layerPatterns(ctx, patterns, {
|
|
2233
2517
|
baseSize: size,
|
|
@@ -2326,7 +2610,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
|
2326
2610
|
bestStyles: [
|
|
2327
2611
|
"fill-only",
|
|
2328
2612
|
"watercolor",
|
|
2329
|
-
"fill-and-stroke"
|
|
2613
|
+
"fill-and-stroke",
|
|
2614
|
+
"hand-drawn"
|
|
2330
2615
|
]
|
|
2331
2616
|
},
|
|
2332
2617
|
square: {
|
|
@@ -2363,7 +2648,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
|
2363
2648
|
bestStyles: [
|
|
2364
2649
|
"fill-and-stroke",
|
|
2365
2650
|
"fill-only",
|
|
2366
|
-
"watercolor"
|
|
2651
|
+
"watercolor",
|
|
2652
|
+
"hand-drawn"
|
|
2367
2653
|
]
|
|
2368
2654
|
},
|
|
2369
2655
|
hexagon: {
|
|
@@ -2743,7 +3029,8 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
|
2743
3029
|
bestStyles: [
|
|
2744
3030
|
"fill-only",
|
|
2745
3031
|
"watercolor",
|
|
2746
|
-
"fill-and-stroke"
|
|
3032
|
+
"fill-and-stroke",
|
|
3033
|
+
"hand-drawn"
|
|
2747
3034
|
]
|
|
2748
3035
|
},
|
|
2749
3036
|
ngon: {
|
|
@@ -3623,8 +3910,51 @@ const $3faa2521b78398cf$var$ARCHETYPES = [
|
|
|
3623
3910
|
invertForeground: false
|
|
3624
3911
|
}
|
|
3625
3912
|
];
|
|
3913
|
+
/**
|
|
3914
|
+
* Linearly interpolate between two archetype numeric parameters.
|
|
3915
|
+
*/ function $3faa2521b78398cf$var$lerpNum(a, b, t) {
|
|
3916
|
+
return a + (b - a) * t;
|
|
3917
|
+
}
|
|
3918
|
+
/**
|
|
3919
|
+
* Blend two archetypes by interpolating their numeric parameters
|
|
3920
|
+
* and merging their style arrays.
|
|
3921
|
+
*/ function $3faa2521b78398cf$var$blendArchetypes(a, b, t) {
|
|
3922
|
+
// Merge preferred styles — unique union, primary archetype first
|
|
3923
|
+
const mergedStyles = [
|
|
3924
|
+
...new Set([
|
|
3925
|
+
...a.preferredStyles,
|
|
3926
|
+
...b.preferredStyles
|
|
3927
|
+
])
|
|
3928
|
+
];
|
|
3929
|
+
return {
|
|
3930
|
+
name: `${a.name}+${b.name}`,
|
|
3931
|
+
gridSize: Math.round($3faa2521b78398cf$var$lerpNum(a.gridSize, b.gridSize, t)),
|
|
3932
|
+
layers: Math.round($3faa2521b78398cf$var$lerpNum(a.layers, b.layers, t)),
|
|
3933
|
+
baseOpacity: $3faa2521b78398cf$var$lerpNum(a.baseOpacity, b.baseOpacity, t),
|
|
3934
|
+
opacityReduction: $3faa2521b78398cf$var$lerpNum(a.opacityReduction, b.opacityReduction, t),
|
|
3935
|
+
minShapeSize: Math.round($3faa2521b78398cf$var$lerpNum(a.minShapeSize, b.minShapeSize, t)),
|
|
3936
|
+
maxShapeSize: Math.round($3faa2521b78398cf$var$lerpNum(a.maxShapeSize, b.maxShapeSize, t)),
|
|
3937
|
+
backgroundStyle: t < 0.5 ? a.backgroundStyle : b.backgroundStyle,
|
|
3938
|
+
paletteMode: t < 0.5 ? a.paletteMode : b.paletteMode,
|
|
3939
|
+
preferredStyles: mergedStyles,
|
|
3940
|
+
flowLineMultiplier: $3faa2521b78398cf$var$lerpNum(a.flowLineMultiplier, b.flowLineMultiplier, t),
|
|
3941
|
+
heroShape: t < 0.5 ? a.heroShape : b.heroShape,
|
|
3942
|
+
glowMultiplier: $3faa2521b78398cf$var$lerpNum(a.glowMultiplier, b.glowMultiplier, t),
|
|
3943
|
+
sizePower: $3faa2521b78398cf$var$lerpNum(a.sizePower, b.sizePower, t),
|
|
3944
|
+
invertForeground: t < 0.5 ? a.invertForeground : b.invertForeground
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3626
3947
|
function $3faa2521b78398cf$export$f1142fd7da4d6590(rng) {
|
|
3627
|
-
|
|
3948
|
+
const primary = $3faa2521b78398cf$var$ARCHETYPES[Math.floor(rng() * $3faa2521b78398cf$var$ARCHETYPES.length)];
|
|
3949
|
+
// ~15% chance of blending with a second archetype
|
|
3950
|
+
if (rng() < 0.15) {
|
|
3951
|
+
const secondary = $3faa2521b78398cf$var$ARCHETYPES[Math.floor(rng() * $3faa2521b78398cf$var$ARCHETYPES.length)];
|
|
3952
|
+
if (secondary.name !== primary.name) {
|
|
3953
|
+
const blendT = 0.25 + rng() * 0.25; // 25-50% blend toward secondary
|
|
3954
|
+
return $3faa2521b78398cf$var$blendArchetypes(primary, secondary, blendT);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
return primary;
|
|
3628
3958
|
}
|
|
3629
3959
|
|
|
3630
3960
|
|
|
@@ -3645,7 +3975,8 @@ const $b623126c6e9cbb71$var$COMPOSITION_MODES = [
|
|
|
3645
3975
|
"flow-field",
|
|
3646
3976
|
"spiral",
|
|
3647
3977
|
"grid-subdivision",
|
|
3648
|
-
"clustered"
|
|
3978
|
+
"clustered",
|
|
3979
|
+
"golden-spiral"
|
|
3649
3980
|
];
|
|
3650
3981
|
// ── Helper: get position based on composition mode ──────────────────
|
|
3651
3982
|
function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
@@ -3703,6 +4034,21 @@ function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height,
|
|
|
3703
4034
|
x: rng() * width,
|
|
3704
4035
|
y: rng() * height
|
|
3705
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
|
+
}
|
|
3706
4052
|
}
|
|
3707
4053
|
}
|
|
3708
4054
|
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
@@ -3960,6 +4306,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3960
4306
|
const colorGrade = (0, $9d614e7d77fc2947$export$6d1620b367f86f7a)(rng);
|
|
3961
4307
|
// ── 0e. Light direction — consistent shadow angle ──────────────
|
|
3962
4308
|
const lightAngle = rng() * Math.PI * 2;
|
|
4309
|
+
// ── 0f. Palette evolution — hue drift direction across layers ──
|
|
4310
|
+
const paletteHueShift = (rng() - 0.5) * 40; // -20° to +20° total drift
|
|
3963
4311
|
const scaleFactor = Math.min(width, height) / 1024;
|
|
3964
4312
|
const adjustedMinSize = minShapeSize * scaleFactor;
|
|
3965
4313
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
@@ -4134,11 +4482,59 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4134
4482
|
ry + (nearest.y - ry) * pull
|
|
4135
4483
|
];
|
|
4136
4484
|
}
|
|
4137
|
-
// ──
|
|
4485
|
+
// ── 3b. Void zone decoration — intentional negative space ────
|
|
4486
|
+
for (const zone of voidZones){
|
|
4487
|
+
// Subtle halo ring around void zones
|
|
4488
|
+
ctx.globalAlpha = 0.04 + rng() * 0.04;
|
|
4489
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
4490
|
+
ctx.lineWidth = 1.5 * scaleFactor;
|
|
4491
|
+
ctx.beginPath();
|
|
4492
|
+
ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2);
|
|
4493
|
+
ctx.stroke();
|
|
4494
|
+
// ~50% chance: scatter tiny dots inside the void
|
|
4495
|
+
if (rng() < 0.5) {
|
|
4496
|
+
const dotCount = 3 + Math.floor(rng() * 6);
|
|
4497
|
+
ctx.globalAlpha = 0.06 + rng() * 0.04;
|
|
4498
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
4499
|
+
for(let d = 0; d < dotCount; d++){
|
|
4500
|
+
const angle = rng() * Math.PI * 2;
|
|
4501
|
+
const dist = rng() * zone.radius * 0.7;
|
|
4502
|
+
const dotR = (1 + rng() * 3) * scaleFactor;
|
|
4503
|
+
ctx.beginPath();
|
|
4504
|
+
ctx.arc(zone.x + Math.cos(angle) * dist, zone.y + Math.sin(angle) * dist, dotR, 0, Math.PI * 2);
|
|
4505
|
+
ctx.fill();
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
// ~30% chance: thin concentric ring inside
|
|
4509
|
+
if (rng() < 0.3) {
|
|
4510
|
+
ctx.globalAlpha = 0.03 + rng() * 0.03;
|
|
4511
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4512
|
+
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4513
|
+
const innerR = zone.radius * (0.4 + rng() * 0.3);
|
|
4514
|
+
ctx.beginPath();
|
|
4515
|
+
ctx.arc(zone.x, zone.y, innerR, 0, Math.PI * 2);
|
|
4516
|
+
ctx.stroke();
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
ctx.globalAlpha = 1;
|
|
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);
|
|
4138
4525
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
4139
|
-
const fieldFreq =
|
|
4526
|
+
const fieldFreq = 1.5 + rng() * 2.5; // noise sampling frequency
|
|
4140
4527
|
function flowAngle(x, y) {
|
|
4141
|
-
|
|
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;
|
|
4142
4538
|
}
|
|
4143
4539
|
// Track all placed shapes for density checks and connecting curves
|
|
4144
4540
|
const shapePositions = [];
|
|
@@ -4172,7 +4568,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4172
4568
|
glowColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
4173
4569
|
gradientFillEnd: (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
4174
4570
|
renderStyle: heroStyle,
|
|
4175
|
-
rng: rng
|
|
4571
|
+
rng: rng,
|
|
4572
|
+
lightAngle: lightAngle,
|
|
4573
|
+
scaleFactor: scaleFactor
|
|
4176
4574
|
});
|
|
4177
4575
|
heroCenter = {
|
|
4178
4576
|
x: heroFocal.x,
|
|
@@ -4206,6 +4604,18 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4206
4604
|
const dofFactor = 1 - layerRatio * 0.5; // 1.0 for front layer, 0.5 for back
|
|
4207
4605
|
const dofStrokeScale = 0.4 + dofFactor * 0.6; // strokes thin out with depth
|
|
4208
4606
|
const dofContrastReduction = layerRatio * 0.2; // colors fade toward bg
|
|
4607
|
+
// Color palette evolution — hue-rotate the hierarchy per layer
|
|
4608
|
+
const layerHierarchy = (0, $9d614e7d77fc2947$export$703ba40a4347f77a)(colorHierarchy, layerRatio, paletteHueShift);
|
|
4609
|
+
// Focal depth: shapes near focal points get more detail
|
|
4610
|
+
const focalDetailBoost = (px, py)=>{
|
|
4611
|
+
let minFocalDist = Infinity;
|
|
4612
|
+
for (const fp of focalPoints){
|
|
4613
|
+
const d = Math.hypot(px - fp.x, py - fp.y);
|
|
4614
|
+
if (d < minFocalDist) minFocalDist = d;
|
|
4615
|
+
}
|
|
4616
|
+
const maxDist = Math.hypot(width, height) * 0.5;
|
|
4617
|
+
return Math.max(0, 1 - minFocalDist / maxDist); // 1.0 at focal, 0.0 at edges
|
|
4618
|
+
};
|
|
4209
4619
|
for(let i = 0; i < numShapes; i++){
|
|
4210
4620
|
// Position from composition mode, then focal bias
|
|
4211
4621
|
const rawPos = $b623126c6e9cbb71$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
@@ -4219,7 +4629,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4219
4629
|
}
|
|
4220
4630
|
// Power distribution for size — archetype controls the curve
|
|
4221
4631
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
4222
|
-
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
4632
|
+
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale * noiseSizeModulation(x, y);
|
|
4223
4633
|
// Size fraction for affinity-aware shape selection
|
|
4224
4634
|
const sizeFraction = size / adjustedMaxSize;
|
|
4225
4635
|
// Palette-driven shape selection (replaces naive pickShape)
|
|
@@ -4236,9 +4646,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4236
4646
|
rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
|
|
4237
4647
|
}
|
|
4238
4648
|
}
|
|
4239
|
-
// Positional color from hierarchy + jitter
|
|
4240
|
-
let fillBase = $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height,
|
|
4241
|
-
const strokeBase = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(
|
|
4649
|
+
// Positional color from hierarchy + jitter (using evolved layer palette)
|
|
4650
|
+
let fillBase = $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, layerHierarchy, rng);
|
|
4651
|
+
const strokeBase = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(layerHierarchy, rng);
|
|
4242
4652
|
// Desaturate colors on later layers for depth
|
|
4243
4653
|
if (atmosphericDesat > 0) fillBase = (0, $9d614e7d77fc2947$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
4244
4654
|
// Temperature contrast: shift foreground shapes opposite to background
|
|
@@ -4322,7 +4732,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4322
4732
|
glowColor: hasGlow ? (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
4323
4733
|
gradientFillEnd: gradientEnd,
|
|
4324
4734
|
renderStyle: finalRenderStyle,
|
|
4325
|
-
rng: rng
|
|
4735
|
+
rng: rng,
|
|
4736
|
+
lightAngle: lightAngle,
|
|
4737
|
+
scaleFactor: scaleFactor
|
|
4326
4738
|
};
|
|
4327
4739
|
if (shouldMirror) (0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4328
4740
|
...shapeConfig,
|
|
@@ -4330,6 +4742,25 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4330
4742
|
mirrorGap: size * (0.1 + rng() * 0.3)
|
|
4331
4743
|
});
|
|
4332
4744
|
else (0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
|
|
4745
|
+
// ── Glazing — luminous multi-pass transparency on ~20% of shapes ──
|
|
4746
|
+
if (rng() < 0.2 && size > adjustedMinSize * 2) {
|
|
4747
|
+
const glazePasses = 2 + Math.floor(rng() * 2);
|
|
4748
|
+
for(let g = 0; g < glazePasses; g++){
|
|
4749
|
+
const glazeScale = 1 - (g + 1) * 0.12; // progressively smaller
|
|
4750
|
+
const glazeAlpha = 0.08 + g * 0.04; // progressively more opaque toward center
|
|
4751
|
+
ctx.globalAlpha = glazeAlpha;
|
|
4752
|
+
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, {
|
|
4753
|
+
fillColor: (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.15 + g * 0.1),
|
|
4754
|
+
strokeColor: "rgba(0,0,0,0)",
|
|
4755
|
+
strokeWidth: 0,
|
|
4756
|
+
size: size * glazeScale,
|
|
4757
|
+
rotation: rotation,
|
|
4758
|
+
proportionType: "GOLDEN_RATIO",
|
|
4759
|
+
renderStyle: "fill-only",
|
|
4760
|
+
rng: rng
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4333
4764
|
shapePositions.push({
|
|
4334
4765
|
x: finalX,
|
|
4335
4766
|
y: finalY,
|
|
@@ -4367,7 +4798,10 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4367
4798
|
}
|
|
4368
4799
|
}
|
|
4369
4800
|
// ── 5d. Recursive nesting ──────────────────────────────────
|
|
4370
|
-
|
|
4801
|
+
// Focal depth: shapes near focal points get more detail
|
|
4802
|
+
const focalProximity = focalDetailBoost(finalX, finalY);
|
|
4803
|
+
const nestingChance = 0.15 + focalProximity * 0.15; // 15-30% near focal
|
|
4804
|
+
if (size > adjustedMaxSize * 0.4 && rng() < nestingChance) {
|
|
4371
4805
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
4372
4806
|
for(let n = 0; n < innerCount; n++){
|
|
4373
4807
|
// Pick inner shape from palette affinities
|
|
@@ -4392,7 +4826,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4392
4826
|
}
|
|
4393
4827
|
}
|
|
4394
4828
|
// ── 5e. Shape constellations — pre-composed groups ─────────
|
|
4395
|
-
|
|
4829
|
+
const constellationChance = 0.12 + focalProximity * 0.1; // 12-22% near focal
|
|
4830
|
+
if (size > adjustedMaxSize * 0.35 && rng() < constellationChance) {
|
|
4396
4831
|
const constellation = $b623126c6e9cbb71$var$CONSTELLATIONS[Math.floor(rng() * $b623126c6e9cbb71$var$CONSTELLATIONS.length)];
|
|
4397
4832
|
const members = constellation.build(rng, size);
|
|
4398
4833
|
const groupRotation = rng() * Math.PI * 2;
|
|
@@ -4430,6 +4865,64 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4430
4865
|
}
|
|
4431
4866
|
// Reset blend mode for post-processing passes
|
|
4432
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
|
+
}
|
|
4433
4926
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
4434
4927
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
4435
4928
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
@@ -4496,7 +4989,41 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4496
4989
|
prevY = fy;
|
|
4497
4990
|
}
|
|
4498
4991
|
}
|
|
4499
|
-
// ── 6b.
|
|
4992
|
+
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
4993
|
+
const energyArchetypes = [
|
|
4994
|
+
"dense-chaotic",
|
|
4995
|
+
"cosmic",
|
|
4996
|
+
"neon-glow",
|
|
4997
|
+
"bold-graphic"
|
|
4998
|
+
];
|
|
4999
|
+
const hasEnergyLines = energyArchetypes.some((a)=>archetype.name.includes(a)) || rng() < 0.25;
|
|
5000
|
+
if (hasEnergyLines && shapePositions.length > 0) {
|
|
5001
|
+
const energyCount = 5 + Math.floor(rng() * 10);
|
|
5002
|
+
ctx.lineCap = "round";
|
|
5003
|
+
for(let e = 0; e < energyCount; e++){
|
|
5004
|
+
// Pick a random shape to radiate from
|
|
5005
|
+
const source = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5006
|
+
const burstCount = 2 + Math.floor(rng() * 4);
|
|
5007
|
+
const baseAngle = flowAngle(source.x, source.y);
|
|
5008
|
+
for(let b = 0; b < burstCount; b++){
|
|
5009
|
+
const angle = baseAngle + (rng() - 0.5) * 1.2;
|
|
5010
|
+
const lineLen = (source.size * 0.3 + rng() * source.size * 0.5) * scaleFactor * 0.3;
|
|
5011
|
+
const startDist = source.size * 0.5;
|
|
5012
|
+
const sx = source.x + Math.cos(angle) * startDist;
|
|
5013
|
+
const sy = source.y + Math.sin(angle) * startDist;
|
|
5014
|
+
const ex = sx + Math.cos(angle) * lineLen;
|
|
5015
|
+
const ey = sy + Math.sin(angle) * lineLen;
|
|
5016
|
+
ctx.globalAlpha = 0.04 + rng() * 0.06;
|
|
5017
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$90ad0e6170cf6af5)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
5018
|
+
ctx.lineWidth = (0.5 + rng() * 1.5) * scaleFactor;
|
|
5019
|
+
ctx.beginPath();
|
|
5020
|
+
ctx.moveTo(sx, sy);
|
|
5021
|
+
ctx.lineTo(ex, ey);
|
|
5022
|
+
ctx.stroke();
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
4500
5027
|
if (symmetryMode !== "none") {
|
|
4501
5028
|
const canvas = ctx.canvas;
|
|
4502
5029
|
ctx.save();
|
|
@@ -4607,6 +5134,214 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4607
5134
|
ctx.restore();
|
|
4608
5135
|
ctx.globalCompositeOperation = "source-over";
|
|
4609
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
|
+
}
|
|
5307
|
+
// ── 11. Signature mark — unique geometric chop from hash prefix ──
|
|
5308
|
+
{
|
|
5309
|
+
const sigRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 42));
|
|
5310
|
+
const sigSize = Math.min(width, height) * 0.025;
|
|
5311
|
+
// Bottom-right corner with padding
|
|
5312
|
+
const sigX = width - sigSize * 2.5;
|
|
5313
|
+
const sigY = height - sigSize * 2.5;
|
|
5314
|
+
const sigSegments = 4 + Math.floor(sigRng() * 4); // 4-7 segments
|
|
5315
|
+
const sigColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.accent, 0.15);
|
|
5316
|
+
ctx.save();
|
|
5317
|
+
ctx.globalAlpha = 0.12 + sigRng() * 0.08;
|
|
5318
|
+
ctx.translate(sigX, sigY);
|
|
5319
|
+
ctx.strokeStyle = sigColor;
|
|
5320
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.06);
|
|
5321
|
+
ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
|
|
5322
|
+
// Outer ring
|
|
5323
|
+
ctx.beginPath();
|
|
5324
|
+
ctx.arc(0, 0, sigSize, 0, Math.PI * 2);
|
|
5325
|
+
ctx.stroke();
|
|
5326
|
+
ctx.fill();
|
|
5327
|
+
// Inner geometric pattern — unique per hash
|
|
5328
|
+
ctx.beginPath();
|
|
5329
|
+
for(let s = 0; s < sigSegments; s++){
|
|
5330
|
+
const angle1 = sigRng() * Math.PI * 2;
|
|
5331
|
+
const angle2 = sigRng() * Math.PI * 2;
|
|
5332
|
+
const r1 = sigSize * (0.2 + sigRng() * 0.6);
|
|
5333
|
+
const r2 = sigSize * (0.2 + sigRng() * 0.6);
|
|
5334
|
+
ctx.moveTo(Math.cos(angle1) * r1, Math.sin(angle1) * r1);
|
|
5335
|
+
ctx.lineTo(Math.cos(angle2) * r2, Math.sin(angle2) * r2);
|
|
5336
|
+
}
|
|
5337
|
+
ctx.stroke();
|
|
5338
|
+
// Center dot
|
|
5339
|
+
ctx.beginPath();
|
|
5340
|
+
ctx.arc(0, 0, sigSize * 0.12, 0, Math.PI * 2);
|
|
5341
|
+
ctx.fillStyle = sigColor;
|
|
5342
|
+
ctx.fill();
|
|
5343
|
+
ctx.restore();
|
|
5344
|
+
}
|
|
4610
5345
|
ctx.globalAlpha = 1;
|
|
4611
5346
|
}
|
|
4612
5347
|
|