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/browser.js
CHANGED
|
@@ -61,6 +61,136 @@ const $616009579e3d72c5$export$bb9e4790bc99ae59 = {
|
|
|
61
61
|
PI: Math.PI,
|
|
62
62
|
PHI: (1 + Math.sqrt(5)) / 2
|
|
63
63
|
};
|
|
64
|
+
function $616009579e3d72c5$export$bbde7fbaaf9a8d66(rng) {
|
|
65
|
+
// Build a deterministic permutation table (256 entries, doubled)
|
|
66
|
+
const perm = new Uint8Array(512);
|
|
67
|
+
const p = new Uint8Array(256);
|
|
68
|
+
for(let i = 0; i < 256; i++)p[i] = i;
|
|
69
|
+
// Fisher-Yates shuffle with our seeded RNG
|
|
70
|
+
for(let i = 255; i > 0; i--){
|
|
71
|
+
const j = Math.floor(rng() * (i + 1));
|
|
72
|
+
const tmp = p[i];
|
|
73
|
+
p[i] = p[j];
|
|
74
|
+
p[j] = tmp;
|
|
75
|
+
}
|
|
76
|
+
for(let i = 0; i < 512; i++)perm[i] = p[i & 255];
|
|
77
|
+
// 12 gradient vectors for 2D simplex
|
|
78
|
+
const GRAD2 = [
|
|
79
|
+
[
|
|
80
|
+
1,
|
|
81
|
+
1
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
-1,
|
|
85
|
+
1
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
1,
|
|
89
|
+
-1
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
-1,
|
|
93
|
+
-1
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
1,
|
|
97
|
+
0
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
-1,
|
|
101
|
+
0
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
0,
|
|
105
|
+
1
|
|
106
|
+
],
|
|
107
|
+
[
|
|
108
|
+
0,
|
|
109
|
+
-1
|
|
110
|
+
],
|
|
111
|
+
[
|
|
112
|
+
1,
|
|
113
|
+
1
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
-1,
|
|
117
|
+
1
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
1,
|
|
121
|
+
-1
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
-1,
|
|
125
|
+
-1
|
|
126
|
+
]
|
|
127
|
+
];
|
|
128
|
+
const F2 = 0.5 * (Math.sqrt(3) - 1);
|
|
129
|
+
const G2 = (3 - Math.sqrt(3)) / 6;
|
|
130
|
+
function dot2(g, x, y) {
|
|
131
|
+
return g[0] * x + g[1] * y;
|
|
132
|
+
}
|
|
133
|
+
return function noise2D(xin, yin) {
|
|
134
|
+
const s = (xin + yin) * F2;
|
|
135
|
+
const i = Math.floor(xin + s);
|
|
136
|
+
const j = Math.floor(yin + s);
|
|
137
|
+
const t = (i + j) * G2;
|
|
138
|
+
const X0 = i - t;
|
|
139
|
+
const Y0 = j - t;
|
|
140
|
+
const x0 = xin - X0;
|
|
141
|
+
const y0 = yin - Y0;
|
|
142
|
+
let i1, j1;
|
|
143
|
+
if (x0 > y0) {
|
|
144
|
+
i1 = 1;
|
|
145
|
+
j1 = 0;
|
|
146
|
+
} else {
|
|
147
|
+
i1 = 0;
|
|
148
|
+
j1 = 1;
|
|
149
|
+
}
|
|
150
|
+
const x1 = x0 - i1 + G2;
|
|
151
|
+
const y1 = y0 - j1 + G2;
|
|
152
|
+
const x2 = x0 - 1 + 2 * G2;
|
|
153
|
+
const y2 = y0 - 1 + 2 * G2;
|
|
154
|
+
const ii = i & 255;
|
|
155
|
+
const jj = j & 255;
|
|
156
|
+
let n0 = 0, n1 = 0, n2 = 0;
|
|
157
|
+
let t0 = 0.5 - x0 * x0 - y0 * y0;
|
|
158
|
+
if (t0 >= 0) {
|
|
159
|
+
t0 *= t0;
|
|
160
|
+
const gi0 = perm[ii + perm[jj]] % 12;
|
|
161
|
+
n0 = t0 * t0 * dot2(GRAD2[gi0], x0, y0);
|
|
162
|
+
}
|
|
163
|
+
let t1 = 0.5 - x1 * x1 - y1 * y1;
|
|
164
|
+
if (t1 >= 0) {
|
|
165
|
+
t1 *= t1;
|
|
166
|
+
const gi1 = perm[ii + i1 + perm[jj + j1]] % 12;
|
|
167
|
+
n1 = t1 * t1 * dot2(GRAD2[gi1], x1, y1);
|
|
168
|
+
}
|
|
169
|
+
let t2 = 0.5 - x2 * x2 - y2 * y2;
|
|
170
|
+
if (t2 >= 0) {
|
|
171
|
+
t2 *= t2;
|
|
172
|
+
const gi2 = perm[ii + 1 + perm[jj + 1]] % 12;
|
|
173
|
+
n2 = t2 * t2 * dot2(GRAD2[gi2], x2, y2);
|
|
174
|
+
}
|
|
175
|
+
// Scale to approximately [-1, 1]
|
|
176
|
+
return 70 * (n0 + n1 + n2);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function $616009579e3d72c5$export$c81d639e83a19b85(noise, octaves = 4, lacunarity = 2.0, gain = 0.5) {
|
|
180
|
+
return function fbm(x, y) {
|
|
181
|
+
let value = 0;
|
|
182
|
+
let amplitude = 1;
|
|
183
|
+
let frequency = 1;
|
|
184
|
+
let maxAmp = 0;
|
|
185
|
+
for(let i = 0; i < octaves; i++){
|
|
186
|
+
value += noise(x * frequency, y * frequency) * amplitude;
|
|
187
|
+
maxAmp += amplitude;
|
|
188
|
+
amplitude *= gain;
|
|
189
|
+
frequency *= lacunarity;
|
|
190
|
+
}
|
|
191
|
+
return value / maxAmp;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
64
194
|
class $616009579e3d72c5$export$da2372f11bc66b3f {
|
|
65
195
|
static getProportionalSize(baseSize, proportion) {
|
|
66
196
|
return baseSize * proportion;
|
|
@@ -254,6 +384,48 @@ class $b5a262d09b87e373$export$ab958c550f521376 {
|
|
|
254
384
|
$b5a262d09b87e373$var$hslToHex(baseHue, 0.7, 0.35)
|
|
255
385
|
];
|
|
256
386
|
}
|
|
387
|
+
case "split-complementary":
|
|
388
|
+
{
|
|
389
|
+
// Base hue + two colors flanking the complement (±30°)
|
|
390
|
+
const comp = (baseHue + 180) % 360;
|
|
391
|
+
const split1 = (comp - 30 + 360) % 360;
|
|
392
|
+
const split2 = (comp + 30) % 360;
|
|
393
|
+
const sat = 0.55 + this.rng() * 0.25;
|
|
394
|
+
return [
|
|
395
|
+
$b5a262d09b87e373$var$hslToHex(baseHue, sat, 0.5),
|
|
396
|
+
$b5a262d09b87e373$var$hslToHex(baseHue, sat * 0.8, 0.65),
|
|
397
|
+
$b5a262d09b87e373$var$hslToHex(split1, sat, 0.5),
|
|
398
|
+
$b5a262d09b87e373$var$hslToHex(split2, sat, 0.5),
|
|
399
|
+
$b5a262d09b87e373$var$hslToHex(split1, sat * 0.7, 0.7)
|
|
400
|
+
];
|
|
401
|
+
}
|
|
402
|
+
case "analogous-accent":
|
|
403
|
+
{
|
|
404
|
+
// Tight cluster of 3 analogous hues + 1 distant accent
|
|
405
|
+
const step = 15 + this.rng() * 20; // 15-35° apart
|
|
406
|
+
const h1 = (baseHue - step + 360) % 360;
|
|
407
|
+
const h2 = (baseHue + step) % 360;
|
|
408
|
+
const accentHue = (baseHue + 150 + this.rng() * 60) % 360;
|
|
409
|
+
const sat = 0.5 + this.rng() * 0.3;
|
|
410
|
+
return [
|
|
411
|
+
$b5a262d09b87e373$var$hslToHex(baseHue, sat, 0.5),
|
|
412
|
+
$b5a262d09b87e373$var$hslToHex(h1, sat, 0.55),
|
|
413
|
+
$b5a262d09b87e373$var$hslToHex(h2, sat, 0.45),
|
|
414
|
+
$b5a262d09b87e373$var$hslToHex(accentHue, sat + 0.15, 0.5)
|
|
415
|
+
];
|
|
416
|
+
}
|
|
417
|
+
case "limited-palette":
|
|
418
|
+
{
|
|
419
|
+
// Only 3 colors — like a risograph print
|
|
420
|
+
const h2 = (baseHue + 120 + this.rng() * 40) % 360;
|
|
421
|
+
const h3 = (baseHue + 220 + this.rng() * 40) % 360;
|
|
422
|
+
const sat = 0.6 + this.rng() * 0.2;
|
|
423
|
+
return [
|
|
424
|
+
$b5a262d09b87e373$var$hslToHex(baseHue, sat, 0.5),
|
|
425
|
+
$b5a262d09b87e373$var$hslToHex(h2, sat, 0.5),
|
|
426
|
+
$b5a262d09b87e373$var$hslToHex(h3, sat * 0.9, 0.55)
|
|
427
|
+
];
|
|
428
|
+
}
|
|
257
429
|
case "harmonious":
|
|
258
430
|
default:
|
|
259
431
|
return this.getColors();
|
|
@@ -274,6 +446,14 @@ class $b5a262d09b87e373$export$ab958c550f521376 {
|
|
|
274
446
|
"#f5f5f0",
|
|
275
447
|
"#e8e8e0"
|
|
276
448
|
];
|
|
449
|
+
case "split-complementary":
|
|
450
|
+
case "analogous-accent":
|
|
451
|
+
return this.getBackgroundColors();
|
|
452
|
+
case "limited-palette":
|
|
453
|
+
return [
|
|
454
|
+
$b5a262d09b87e373$var$hslToHex(this.seed % 360, 0.08, 0.94),
|
|
455
|
+
$b5a262d09b87e373$var$hslToHex((this.seed + 20) % 360, 0.06, 0.90)
|
|
456
|
+
];
|
|
277
457
|
case "neon":
|
|
278
458
|
return [
|
|
279
459
|
"#0a0a12",
|
|
@@ -518,6 +698,19 @@ function $b5a262d09b87e373$export$6d1620b367f86f7a(rng) {
|
|
|
518
698
|
intensity: intensity
|
|
519
699
|
};
|
|
520
700
|
}
|
|
701
|
+
function $b5a262d09b87e373$export$1793a1bfbe4f6ff5(hex, degrees) {
|
|
702
|
+
const [h, s, l] = $b5a262d09b87e373$var$hexToHsl(hex);
|
|
703
|
+
return $b5a262d09b87e373$var$hslToHex((h + degrees + 360) % 360, s, l);
|
|
704
|
+
}
|
|
705
|
+
function $b5a262d09b87e373$export$703ba40a4347f77a(base, layerRatio, hueShiftPerLayer) {
|
|
706
|
+
const shift = layerRatio * hueShiftPerLayer;
|
|
707
|
+
return {
|
|
708
|
+
dominant: $b5a262d09b87e373$export$1793a1bfbe4f6ff5(base.dominant, shift),
|
|
709
|
+
secondary: $b5a262d09b87e373$export$1793a1bfbe4f6ff5(base.secondary, shift * 0.7),
|
|
710
|
+
accent: $b5a262d09b87e373$export$1793a1bfbe4f6ff5(base.accent, shift * 0.5),
|
|
711
|
+
all: base.all.map((c)=>$b5a262d09b87e373$export$1793a1bfbe4f6ff5(c, shift * 0.6))
|
|
712
|
+
};
|
|
713
|
+
}
|
|
521
714
|
|
|
522
715
|
|
|
523
716
|
|
|
@@ -1801,7 +1994,8 @@ const $e0f99502ff383dd8$var$RENDER_STYLES = [
|
|
|
1801
1994
|
"noise-grain",
|
|
1802
1995
|
"wood-grain",
|
|
1803
1996
|
"marble-vein",
|
|
1804
|
-
"fabric-weave"
|
|
1997
|
+
"fabric-weave",
|
|
1998
|
+
"hand-drawn"
|
|
1805
1999
|
];
|
|
1806
2000
|
function $e0f99502ff383dd8$export$9fd4e64b2acd410e(rng) {
|
|
1807
2001
|
return $e0f99502ff383dd8$var$RENDER_STYLES[Math.floor(rng() * $e0f99502ff383dd8$var$RENDER_STYLES.length)];
|
|
@@ -1897,6 +2091,23 @@ function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
1897
2091
|
ctx.fill();
|
|
1898
2092
|
ctx.fillStyle = origFill;
|
|
1899
2093
|
ctx.restore();
|
|
2094
|
+
// Pass 4: Organic edge erosion — irregular bites along the boundary
|
|
2095
|
+
if (rng && size > 20) {
|
|
2096
|
+
const erosionBites = 6 + Math.floor(rng() * 8);
|
|
2097
|
+
const edgeRadius = size * 0.45;
|
|
2098
|
+
ctx.save();
|
|
2099
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2100
|
+
ctx.globalAlpha = 0.6 + rng() * 0.3;
|
|
2101
|
+
for(let eb = 0; eb < erosionBites; eb++){
|
|
2102
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
2103
|
+
const biteDist = edgeRadius * (0.85 + rng() * 0.25);
|
|
2104
|
+
const biteR = size * (0.02 + rng() * 0.04);
|
|
2105
|
+
ctx.beginPath();
|
|
2106
|
+
ctx.arc(Math.cos(biteAngle) * biteDist, Math.sin(biteAngle) * biteDist, biteR, 0, Math.PI * 2);
|
|
2107
|
+
ctx.fill();
|
|
2108
|
+
}
|
|
2109
|
+
ctx.restore();
|
|
2110
|
+
}
|
|
1900
2111
|
ctx.globalAlpha = savedAlpha;
|
|
1901
2112
|
// Soft stroke on top — thinner than normal for delicacy
|
|
1902
2113
|
ctx.globalAlpha *= 0.25;
|
|
@@ -2182,6 +2393,50 @@ function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2182
2393
|
ctx.globalAlpha /= 0.3;
|
|
2183
2394
|
break;
|
|
2184
2395
|
}
|
|
2396
|
+
case "hand-drawn":
|
|
2397
|
+
{
|
|
2398
|
+
// Wobbly hand-drawn edge treatment — fill normally, then redraw
|
|
2399
|
+
// the outline with perturbed control points for a sketchy feel
|
|
2400
|
+
const savedAlphaHD = ctx.globalAlpha;
|
|
2401
|
+
ctx.globalAlpha = savedAlphaHD * 0.85;
|
|
2402
|
+
ctx.fill();
|
|
2403
|
+
ctx.globalAlpha = savedAlphaHD;
|
|
2404
|
+
// Draw 2-3 slightly offset wobbly strokes for a sketchy look
|
|
2405
|
+
const wobblePasses = 2 + (rng ? Math.floor(rng() * 2) : 0);
|
|
2406
|
+
ctx.lineWidth = strokeWidth * 0.8;
|
|
2407
|
+
for(let wp = 0; wp < wobblePasses; wp++){
|
|
2408
|
+
ctx.globalAlpha = savedAlphaHD * (0.4 - wp * 0.1);
|
|
2409
|
+
ctx.save();
|
|
2410
|
+
// Slight random offset per pass
|
|
2411
|
+
const wobbleX = rng ? (rng() - 0.5) * size * 0.02 : 0;
|
|
2412
|
+
const wobbleY = rng ? (rng() - 0.5) * size * 0.02 : 0;
|
|
2413
|
+
ctx.translate(wobbleX, wobbleY);
|
|
2414
|
+
// Slightly different scale per pass for edge variation
|
|
2415
|
+
const wobbleScale = 1 + (rng ? (rng() - 0.5) * 0.03 : 0);
|
|
2416
|
+
ctx.scale(wobbleScale, wobbleScale);
|
|
2417
|
+
ctx.stroke();
|
|
2418
|
+
ctx.restore();
|
|
2419
|
+
}
|
|
2420
|
+
// Organic edge erosion — small irregular bites for rough paper feel
|
|
2421
|
+
if (rng && size > 20) {
|
|
2422
|
+
const erosionBites = 4 + Math.floor(rng() * 6);
|
|
2423
|
+
const edgeRadius = size * 0.42;
|
|
2424
|
+
ctx.save();
|
|
2425
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
2426
|
+
ctx.globalAlpha = 0.5 + rng() * 0.3;
|
|
2427
|
+
for(let eb = 0; eb < erosionBites; eb++){
|
|
2428
|
+
const biteAngle = rng() * Math.PI * 2;
|
|
2429
|
+
const biteDist = edgeRadius * (0.9 + rng() * 0.2);
|
|
2430
|
+
const biteR = size * (0.015 + rng() * 0.03);
|
|
2431
|
+
ctx.beginPath();
|
|
2432
|
+
ctx.arc(Math.cos(biteAngle) * biteDist, Math.sin(biteAngle) * biteDist, biteR, 0, Math.PI * 2);
|
|
2433
|
+
ctx.fill();
|
|
2434
|
+
}
|
|
2435
|
+
ctx.restore();
|
|
2436
|
+
}
|
|
2437
|
+
ctx.globalAlpha = savedAlphaHD;
|
|
2438
|
+
break;
|
|
2439
|
+
}
|
|
2185
2440
|
case "fill-and-stroke":
|
|
2186
2441
|
default:
|
|
2187
2442
|
ctx.fill();
|
|
@@ -2190,12 +2445,20 @@ function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2190
2445
|
}
|
|
2191
2446
|
}
|
|
2192
2447
|
function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
2193
|
-
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;
|
|
2448
|
+
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;
|
|
2194
2449
|
ctx.save();
|
|
2195
2450
|
ctx.translate(x, y);
|
|
2196
2451
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2197
|
-
//
|
|
2198
|
-
if (
|
|
2452
|
+
// ── Drop shadow — soft colored shadow offset along light direction ──
|
|
2453
|
+
if (lightAngle !== undefined && size > 10) {
|
|
2454
|
+
const shadowDist = size * 0.035;
|
|
2455
|
+
const shadowBlurR = size * 0.06;
|
|
2456
|
+
ctx.shadowOffsetX = Math.cos(lightAngle + Math.PI) * shadowDist;
|
|
2457
|
+
ctx.shadowOffsetY = Math.sin(lightAngle + Math.PI) * shadowDist;
|
|
2458
|
+
ctx.shadowBlur = shadowBlurR;
|
|
2459
|
+
ctx.shadowColor = "rgba(0,0,0,0.12)";
|
|
2460
|
+
} else if (glowRadius > 0) {
|
|
2461
|
+
// Glow / shadow effect (legacy path)
|
|
2199
2462
|
ctx.shadowBlur = glowRadius;
|
|
2200
2463
|
ctx.shadowColor = glowColor || fillColor;
|
|
2201
2464
|
ctx.shadowOffsetX = 0;
|
|
@@ -2217,8 +2480,29 @@ function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2217
2480
|
});
|
|
2218
2481
|
$e0f99502ff383dd8$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
2219
2482
|
}
|
|
2220
|
-
// Reset shadow so patterns aren't double-
|
|
2221
|
-
|
|
2483
|
+
// Reset shadow so patterns and highlight aren't double-shadowed
|
|
2484
|
+
ctx.shadowBlur = 0;
|
|
2485
|
+
ctx.shadowOffsetX = 0;
|
|
2486
|
+
ctx.shadowOffsetY = 0;
|
|
2487
|
+
ctx.shadowColor = "transparent";
|
|
2488
|
+
// ── Specular highlight — bright arc on the light-facing side ──
|
|
2489
|
+
if (lightAngle !== undefined && size > 15 && rng) {
|
|
2490
|
+
const hlRadius = size * 0.35;
|
|
2491
|
+
const hlDist = size * 0.15;
|
|
2492
|
+
const hlX = Math.cos(lightAngle) * hlDist;
|
|
2493
|
+
const hlY = Math.sin(lightAngle) * hlDist;
|
|
2494
|
+
const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, hlRadius);
|
|
2495
|
+
hlGrad.addColorStop(0, "rgba(255,255,255,0.18)");
|
|
2496
|
+
hlGrad.addColorStop(0.5, "rgba(255,255,255,0.05)");
|
|
2497
|
+
hlGrad.addColorStop(1, "rgba(255,255,255,0)");
|
|
2498
|
+
const savedOp = ctx.globalCompositeOperation;
|
|
2499
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
2500
|
+
ctx.fillStyle = hlGrad;
|
|
2501
|
+
ctx.beginPath();
|
|
2502
|
+
ctx.arc(hlX, hlY, hlRadius, 0, Math.PI * 2);
|
|
2503
|
+
ctx.fill();
|
|
2504
|
+
ctx.globalCompositeOperation = savedOp;
|
|
2505
|
+
}
|
|
2222
2506
|
// Layer additional patterns if specified
|
|
2223
2507
|
if (patterns.length > 0) (0, $616009579e3d72c5$export$da2372f11bc66b3f).layerPatterns(ctx, patterns, {
|
|
2224
2508
|
baseSize: size,
|
|
@@ -2317,7 +2601,8 @@ const $8286059160ee2e04$export$4343b39fe47bd82c = {
|
|
|
2317
2601
|
bestStyles: [
|
|
2318
2602
|
"fill-only",
|
|
2319
2603
|
"watercolor",
|
|
2320
|
-
"fill-and-stroke"
|
|
2604
|
+
"fill-and-stroke",
|
|
2605
|
+
"hand-drawn"
|
|
2321
2606
|
]
|
|
2322
2607
|
},
|
|
2323
2608
|
square: {
|
|
@@ -2354,7 +2639,8 @@ const $8286059160ee2e04$export$4343b39fe47bd82c = {
|
|
|
2354
2639
|
bestStyles: [
|
|
2355
2640
|
"fill-and-stroke",
|
|
2356
2641
|
"fill-only",
|
|
2357
|
-
"watercolor"
|
|
2642
|
+
"watercolor",
|
|
2643
|
+
"hand-drawn"
|
|
2358
2644
|
]
|
|
2359
2645
|
},
|
|
2360
2646
|
hexagon: {
|
|
@@ -2734,7 +3020,8 @@ const $8286059160ee2e04$export$4343b39fe47bd82c = {
|
|
|
2734
3020
|
bestStyles: [
|
|
2735
3021
|
"fill-only",
|
|
2736
3022
|
"watercolor",
|
|
2737
|
-
"fill-and-stroke"
|
|
3023
|
+
"fill-and-stroke",
|
|
3024
|
+
"hand-drawn"
|
|
2738
3025
|
]
|
|
2739
3026
|
},
|
|
2740
3027
|
ngon: {
|
|
@@ -3614,8 +3901,51 @@ const $68a238ccd77f2bcd$var$ARCHETYPES = [
|
|
|
3614
3901
|
invertForeground: false
|
|
3615
3902
|
}
|
|
3616
3903
|
];
|
|
3904
|
+
/**
|
|
3905
|
+
* Linearly interpolate between two archetype numeric parameters.
|
|
3906
|
+
*/ function $68a238ccd77f2bcd$var$lerpNum(a, b, t) {
|
|
3907
|
+
return a + (b - a) * t;
|
|
3908
|
+
}
|
|
3909
|
+
/**
|
|
3910
|
+
* Blend two archetypes by interpolating their numeric parameters
|
|
3911
|
+
* and merging their style arrays.
|
|
3912
|
+
*/ function $68a238ccd77f2bcd$var$blendArchetypes(a, b, t) {
|
|
3913
|
+
// Merge preferred styles — unique union, primary archetype first
|
|
3914
|
+
const mergedStyles = [
|
|
3915
|
+
...new Set([
|
|
3916
|
+
...a.preferredStyles,
|
|
3917
|
+
...b.preferredStyles
|
|
3918
|
+
])
|
|
3919
|
+
];
|
|
3920
|
+
return {
|
|
3921
|
+
name: `${a.name}+${b.name}`,
|
|
3922
|
+
gridSize: Math.round($68a238ccd77f2bcd$var$lerpNum(a.gridSize, b.gridSize, t)),
|
|
3923
|
+
layers: Math.round($68a238ccd77f2bcd$var$lerpNum(a.layers, b.layers, t)),
|
|
3924
|
+
baseOpacity: $68a238ccd77f2bcd$var$lerpNum(a.baseOpacity, b.baseOpacity, t),
|
|
3925
|
+
opacityReduction: $68a238ccd77f2bcd$var$lerpNum(a.opacityReduction, b.opacityReduction, t),
|
|
3926
|
+
minShapeSize: Math.round($68a238ccd77f2bcd$var$lerpNum(a.minShapeSize, b.minShapeSize, t)),
|
|
3927
|
+
maxShapeSize: Math.round($68a238ccd77f2bcd$var$lerpNum(a.maxShapeSize, b.maxShapeSize, t)),
|
|
3928
|
+
backgroundStyle: t < 0.5 ? a.backgroundStyle : b.backgroundStyle,
|
|
3929
|
+
paletteMode: t < 0.5 ? a.paletteMode : b.paletteMode,
|
|
3930
|
+
preferredStyles: mergedStyles,
|
|
3931
|
+
flowLineMultiplier: $68a238ccd77f2bcd$var$lerpNum(a.flowLineMultiplier, b.flowLineMultiplier, t),
|
|
3932
|
+
heroShape: t < 0.5 ? a.heroShape : b.heroShape,
|
|
3933
|
+
glowMultiplier: $68a238ccd77f2bcd$var$lerpNum(a.glowMultiplier, b.glowMultiplier, t),
|
|
3934
|
+
sizePower: $68a238ccd77f2bcd$var$lerpNum(a.sizePower, b.sizePower, t),
|
|
3935
|
+
invertForeground: t < 0.5 ? a.invertForeground : b.invertForeground
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3617
3938
|
function $68a238ccd77f2bcd$export$f1142fd7da4d6590(rng) {
|
|
3618
|
-
|
|
3939
|
+
const primary = $68a238ccd77f2bcd$var$ARCHETYPES[Math.floor(rng() * $68a238ccd77f2bcd$var$ARCHETYPES.length)];
|
|
3940
|
+
// ~15% chance of blending with a second archetype
|
|
3941
|
+
if (rng() < 0.15) {
|
|
3942
|
+
const secondary = $68a238ccd77f2bcd$var$ARCHETYPES[Math.floor(rng() * $68a238ccd77f2bcd$var$ARCHETYPES.length)];
|
|
3943
|
+
if (secondary.name !== primary.name) {
|
|
3944
|
+
const blendT = 0.25 + rng() * 0.25; // 25-50% blend toward secondary
|
|
3945
|
+
return $68a238ccd77f2bcd$var$blendArchetypes(primary, secondary, blendT);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
return primary;
|
|
3619
3949
|
}
|
|
3620
3950
|
|
|
3621
3951
|
|
|
@@ -3636,7 +3966,8 @@ const $1f63dc64b5593c73$var$COMPOSITION_MODES = [
|
|
|
3636
3966
|
"flow-field",
|
|
3637
3967
|
"spiral",
|
|
3638
3968
|
"grid-subdivision",
|
|
3639
|
-
"clustered"
|
|
3969
|
+
"clustered",
|
|
3970
|
+
"golden-spiral"
|
|
3640
3971
|
];
|
|
3641
3972
|
// ── Helper: get position based on composition mode ──────────────────
|
|
3642
3973
|
function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
@@ -3694,6 +4025,21 @@ function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height,
|
|
|
3694
4025
|
x: rng() * width,
|
|
3695
4026
|
y: rng() * height
|
|
3696
4027
|
};
|
|
4028
|
+
case "golden-spiral":
|
|
4029
|
+
{
|
|
4030
|
+
// Logarithmic spiral: r = a * e^(b*theta), with golden angle spacing
|
|
4031
|
+
const PHI = (1 + Math.sqrt(5)) / 2;
|
|
4032
|
+
const goldenAngle = 2 * Math.PI / (PHI * PHI); // ~137.5° in radians
|
|
4033
|
+
const t = shapeIndex / totalShapes;
|
|
4034
|
+
const angle = shapeIndex * goldenAngle + rng() * 0.3;
|
|
4035
|
+
const maxR = Math.min(width, height) * 0.44;
|
|
4036
|
+
// Shapes spiral outward with sqrt distribution for even area coverage
|
|
4037
|
+
const r = Math.sqrt(t) * maxR + (rng() - 0.5) * maxR * 0.08;
|
|
4038
|
+
return {
|
|
4039
|
+
x: cx + Math.cos(angle) * r,
|
|
4040
|
+
y: cy + Math.sin(angle) * r
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
3697
4043
|
}
|
|
3698
4044
|
}
|
|
3699
4045
|
// ── Helper: positional color from hierarchy ─────────────────────────
|
|
@@ -3951,6 +4297,8 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
3951
4297
|
const colorGrade = (0, $b5a262d09b87e373$export$6d1620b367f86f7a)(rng);
|
|
3952
4298
|
// ── 0e. Light direction — consistent shadow angle ──────────────
|
|
3953
4299
|
const lightAngle = rng() * Math.PI * 2;
|
|
4300
|
+
// ── 0f. Palette evolution — hue drift direction across layers ──
|
|
4301
|
+
const paletteHueShift = (rng() - 0.5) * 40; // -20° to +20° total drift
|
|
3954
4302
|
const scaleFactor = Math.min(width, height) / 1024;
|
|
3955
4303
|
const adjustedMinSize = minShapeSize * scaleFactor;
|
|
3956
4304
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
@@ -4125,11 +4473,59 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4125
4473
|
ry + (nearest.y - ry) * pull
|
|
4126
4474
|
];
|
|
4127
4475
|
}
|
|
4128
|
-
// ──
|
|
4476
|
+
// ── 3b. Void zone decoration — intentional negative space ────
|
|
4477
|
+
for (const zone of voidZones){
|
|
4478
|
+
// Subtle halo ring around void zones
|
|
4479
|
+
ctx.globalAlpha = 0.04 + rng() * 0.04;
|
|
4480
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
4481
|
+
ctx.lineWidth = 1.5 * scaleFactor;
|
|
4482
|
+
ctx.beginPath();
|
|
4483
|
+
ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2);
|
|
4484
|
+
ctx.stroke();
|
|
4485
|
+
// ~50% chance: scatter tiny dots inside the void
|
|
4486
|
+
if (rng() < 0.5) {
|
|
4487
|
+
const dotCount = 3 + Math.floor(rng() * 6);
|
|
4488
|
+
ctx.globalAlpha = 0.06 + rng() * 0.04;
|
|
4489
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
4490
|
+
for(let d = 0; d < dotCount; d++){
|
|
4491
|
+
const angle = rng() * Math.PI * 2;
|
|
4492
|
+
const dist = rng() * zone.radius * 0.7;
|
|
4493
|
+
const dotR = (1 + rng() * 3) * scaleFactor;
|
|
4494
|
+
ctx.beginPath();
|
|
4495
|
+
ctx.arc(zone.x + Math.cos(angle) * dist, zone.y + Math.sin(angle) * dist, dotR, 0, Math.PI * 2);
|
|
4496
|
+
ctx.fill();
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
// ~30% chance: thin concentric ring inside
|
|
4500
|
+
if (rng() < 0.3) {
|
|
4501
|
+
ctx.globalAlpha = 0.03 + rng() * 0.03;
|
|
4502
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4503
|
+
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4504
|
+
const innerR = zone.radius * (0.4 + rng() * 0.3);
|
|
4505
|
+
ctx.beginPath();
|
|
4506
|
+
ctx.arc(zone.x, zone.y, innerR, 0, Math.PI * 2);
|
|
4507
|
+
ctx.stroke();
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
ctx.globalAlpha = 1;
|
|
4511
|
+
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4512
|
+
// Create a seeded simplex noise field (unique per hash)
|
|
4513
|
+
const noiseFieldRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 333));
|
|
4514
|
+
const simplexNoise = (0, $616009579e3d72c5$export$bbde7fbaaf9a8d66)(noiseFieldRng);
|
|
4515
|
+
const fbmNoise = (0, $616009579e3d72c5$export$c81d639e83a19b85)(simplexNoise, 3, 2.0, 0.5);
|
|
4129
4516
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
4130
|
-
const fieldFreq =
|
|
4517
|
+
const fieldFreq = 1.5 + rng() * 2.5; // noise sampling frequency
|
|
4131
4518
|
function flowAngle(x, y) {
|
|
4132
|
-
|
|
4519
|
+
// Sample FBM noise at the position, scaled by frequency
|
|
4520
|
+
const nx = x / width * fieldFreq;
|
|
4521
|
+
const ny = y / height * fieldFreq;
|
|
4522
|
+
return fieldAngleBase + fbmNoise(nx, ny) * Math.PI;
|
|
4523
|
+
}
|
|
4524
|
+
// Noise-based size modulation — shapes in "high noise" areas get scaled
|
|
4525
|
+
function noiseSizeModulation(x, y) {
|
|
4526
|
+
const n = simplexNoise(x / width * 3, y / height * 3);
|
|
4527
|
+
// Map [-1,1] to [0.7, 1.3] — subtle terrain-like size variation
|
|
4528
|
+
return 0.7 + (n + 1) * 0.3;
|
|
4133
4529
|
}
|
|
4134
4530
|
// Track all placed shapes for density checks and connecting curves
|
|
4135
4531
|
const shapePositions = [];
|
|
@@ -4163,7 +4559,9 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4163
4559
|
glowColor: (0, $b5a262d09b87e373$export$f2121afcad3d553f)(heroStroke, 0.4),
|
|
4164
4560
|
gradientFillEnd: (0, $b5a262d09b87e373$export$18a34c25ea7e724b)(colorHierarchy.secondary, rng, 10, 0.1),
|
|
4165
4561
|
renderStyle: heroStyle,
|
|
4166
|
-
rng: rng
|
|
4562
|
+
rng: rng,
|
|
4563
|
+
lightAngle: lightAngle,
|
|
4564
|
+
scaleFactor: scaleFactor
|
|
4167
4565
|
});
|
|
4168
4566
|
heroCenter = {
|
|
4169
4567
|
x: heroFocal.x,
|
|
@@ -4197,6 +4595,18 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4197
4595
|
const dofFactor = 1 - layerRatio * 0.5; // 1.0 for front layer, 0.5 for back
|
|
4198
4596
|
const dofStrokeScale = 0.4 + dofFactor * 0.6; // strokes thin out with depth
|
|
4199
4597
|
const dofContrastReduction = layerRatio * 0.2; // colors fade toward bg
|
|
4598
|
+
// Color palette evolution — hue-rotate the hierarchy per layer
|
|
4599
|
+
const layerHierarchy = (0, $b5a262d09b87e373$export$703ba40a4347f77a)(colorHierarchy, layerRatio, paletteHueShift);
|
|
4600
|
+
// Focal depth: shapes near focal points get more detail
|
|
4601
|
+
const focalDetailBoost = (px, py)=>{
|
|
4602
|
+
let minFocalDist = Infinity;
|
|
4603
|
+
for (const fp of focalPoints){
|
|
4604
|
+
const d = Math.hypot(px - fp.x, py - fp.y);
|
|
4605
|
+
if (d < minFocalDist) minFocalDist = d;
|
|
4606
|
+
}
|
|
4607
|
+
const maxDist = Math.hypot(width, height) * 0.5;
|
|
4608
|
+
return Math.max(0, 1 - minFocalDist / maxDist); // 1.0 at focal, 0.0 at edges
|
|
4609
|
+
};
|
|
4200
4610
|
for(let i = 0; i < numShapes; i++){
|
|
4201
4611
|
// Position from composition mode, then focal bias
|
|
4202
4612
|
const rawPos = $1f63dc64b5593c73$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
@@ -4210,7 +4620,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4210
4620
|
}
|
|
4211
4621
|
// Power distribution for size — archetype controls the curve
|
|
4212
4622
|
const sizeT = Math.pow(rng(), archetype.sizePower);
|
|
4213
|
-
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
4623
|
+
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale * noiseSizeModulation(x, y);
|
|
4214
4624
|
// Size fraction for affinity-aware shape selection
|
|
4215
4625
|
const sizeFraction = size / adjustedMaxSize;
|
|
4216
4626
|
// Palette-driven shape selection (replaces naive pickShape)
|
|
@@ -4227,9 +4637,9 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4227
4637
|
rotation = rotation + (angleToHero - rotation) * blendFactor * 0.4;
|
|
4228
4638
|
}
|
|
4229
4639
|
}
|
|
4230
|
-
// Positional color from hierarchy + jitter
|
|
4231
|
-
let fillBase = $1f63dc64b5593c73$var$getPositionalColor(x, y, width, height,
|
|
4232
|
-
const strokeBase = (0, $b5a262d09b87e373$export$b49f62f0a99da0e8)(
|
|
4640
|
+
// Positional color from hierarchy + jitter (using evolved layer palette)
|
|
4641
|
+
let fillBase = $1f63dc64b5593c73$var$getPositionalColor(x, y, width, height, layerHierarchy, rng);
|
|
4642
|
+
const strokeBase = (0, $b5a262d09b87e373$export$b49f62f0a99da0e8)(layerHierarchy, rng);
|
|
4233
4643
|
// Desaturate colors on later layers for depth
|
|
4234
4644
|
if (atmosphericDesat > 0) fillBase = (0, $b5a262d09b87e373$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
4235
4645
|
// Temperature contrast: shift foreground shapes opposite to background
|
|
@@ -4313,7 +4723,9 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4313
4723
|
glowColor: hasGlow ? (0, $b5a262d09b87e373$export$f2121afcad3d553f)(fillColor, 0.6) : shadowDist > 0 ? "rgba(0,0,0,0.08)" : undefined,
|
|
4314
4724
|
gradientFillEnd: gradientEnd,
|
|
4315
4725
|
renderStyle: finalRenderStyle,
|
|
4316
|
-
rng: rng
|
|
4726
|
+
rng: rng,
|
|
4727
|
+
lightAngle: lightAngle,
|
|
4728
|
+
scaleFactor: scaleFactor
|
|
4317
4729
|
};
|
|
4318
4730
|
if (shouldMirror) (0, $e0f99502ff383dd8$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
4319
4731
|
...shapeConfig,
|
|
@@ -4321,6 +4733,25 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4321
4733
|
mirrorGap: size * (0.1 + rng() * 0.3)
|
|
4322
4734
|
});
|
|
4323
4735
|
else (0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
|
|
4736
|
+
// ── Glazing — luminous multi-pass transparency on ~20% of shapes ──
|
|
4737
|
+
if (rng() < 0.2 && size > adjustedMinSize * 2) {
|
|
4738
|
+
const glazePasses = 2 + Math.floor(rng() * 2);
|
|
4739
|
+
for(let g = 0; g < glazePasses; g++){
|
|
4740
|
+
const glazeScale = 1 - (g + 1) * 0.12; // progressively smaller
|
|
4741
|
+
const glazeAlpha = 0.08 + g * 0.04; // progressively more opaque toward center
|
|
4742
|
+
ctx.globalAlpha = glazeAlpha;
|
|
4743
|
+
(0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, {
|
|
4744
|
+
fillColor: (0, $b5a262d09b87e373$export$f2121afcad3d553f)(fillColor, 0.15 + g * 0.1),
|
|
4745
|
+
strokeColor: "rgba(0,0,0,0)",
|
|
4746
|
+
strokeWidth: 0,
|
|
4747
|
+
size: size * glazeScale,
|
|
4748
|
+
rotation: rotation,
|
|
4749
|
+
proportionType: "GOLDEN_RATIO",
|
|
4750
|
+
renderStyle: "fill-only",
|
|
4751
|
+
rng: rng
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4324
4755
|
shapePositions.push({
|
|
4325
4756
|
x: finalX,
|
|
4326
4757
|
y: finalY,
|
|
@@ -4358,7 +4789,10 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4358
4789
|
}
|
|
4359
4790
|
}
|
|
4360
4791
|
// ── 5d. Recursive nesting ──────────────────────────────────
|
|
4361
|
-
|
|
4792
|
+
// Focal depth: shapes near focal points get more detail
|
|
4793
|
+
const focalProximity = focalDetailBoost(finalX, finalY);
|
|
4794
|
+
const nestingChance = 0.15 + focalProximity * 0.15; // 15-30% near focal
|
|
4795
|
+
if (size > adjustedMaxSize * 0.4 && rng() < nestingChance) {
|
|
4362
4796
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
4363
4797
|
for(let n = 0; n < innerCount; n++){
|
|
4364
4798
|
// Pick inner shape from palette affinities
|
|
@@ -4383,7 +4817,8 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4383
4817
|
}
|
|
4384
4818
|
}
|
|
4385
4819
|
// ── 5e. Shape constellations — pre-composed groups ─────────
|
|
4386
|
-
|
|
4820
|
+
const constellationChance = 0.12 + focalProximity * 0.1; // 12-22% near focal
|
|
4821
|
+
if (size > adjustedMaxSize * 0.35 && rng() < constellationChance) {
|
|
4387
4822
|
const constellation = $1f63dc64b5593c73$var$CONSTELLATIONS[Math.floor(rng() * $1f63dc64b5593c73$var$CONSTELLATIONS.length)];
|
|
4388
4823
|
const members = constellation.build(rng, size);
|
|
4389
4824
|
const groupRotation = rng() * Math.PI * 2;
|
|
@@ -4421,6 +4856,64 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4421
4856
|
}
|
|
4422
4857
|
// Reset blend mode for post-processing passes
|
|
4423
4858
|
ctx.globalCompositeOperation = "source-over";
|
|
4859
|
+
// ── 5f. Layered masking / cutout portals ───────────────────────
|
|
4860
|
+
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
4861
|
+
// with a tinted background wash, creating a "peek through" effect.
|
|
4862
|
+
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
4863
|
+
const portalCount = 1 + Math.floor(rng() * 2);
|
|
4864
|
+
for(let p = 0; p < portalCount; p++){
|
|
4865
|
+
// Pick a position biased toward placed shapes
|
|
4866
|
+
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
4867
|
+
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
4868
|
+
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
4869
|
+
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
4870
|
+
// Pick a portal shape from the palette
|
|
4871
|
+
const portalShape = (0, $8286059160ee2e04$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
4872
|
+
const portalRotation = rng() * 360;
|
|
4873
|
+
const portalAlpha = 0.6 + rng() * 0.35;
|
|
4874
|
+
ctx.save();
|
|
4875
|
+
ctx.translate(portalX, portalY);
|
|
4876
|
+
ctx.rotate(portalRotation * Math.PI / 180);
|
|
4877
|
+
// Step 1: Clip to the portal shape and fill with background wash
|
|
4878
|
+
ctx.beginPath();
|
|
4879
|
+
(0, $e41b41d8dcf837ad$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
4880
|
+
ctx.clip();
|
|
4881
|
+
// Fill the clipped region with a radial gradient from background colors
|
|
4882
|
+
const portalColor = (0, $b5a262d09b87e373$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
4883
|
+
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
4884
|
+
portalGrad.addColorStop(0, portalColor);
|
|
4885
|
+
portalGrad.addColorStop(1, bgEnd);
|
|
4886
|
+
ctx.globalAlpha = portalAlpha;
|
|
4887
|
+
ctx.fillStyle = portalGrad;
|
|
4888
|
+
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
4889
|
+
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
4890
|
+
if (rng() < 0.5) {
|
|
4891
|
+
const dotCount = 3 + Math.floor(rng() * 5);
|
|
4892
|
+
ctx.globalAlpha = portalAlpha * 0.3;
|
|
4893
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
4894
|
+
for(let d = 0; d < dotCount; d++){
|
|
4895
|
+
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
4896
|
+
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
4897
|
+
const dr = (1 + rng() * 3) * scaleFactor;
|
|
4898
|
+
ctx.beginPath();
|
|
4899
|
+
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
4900
|
+
ctx.fill();
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
ctx.restore();
|
|
4904
|
+
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
4905
|
+
ctx.save();
|
|
4906
|
+
ctx.translate(portalX, portalY);
|
|
4907
|
+
ctx.rotate(portalRotation * Math.PI / 180);
|
|
4908
|
+
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
4909
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
4910
|
+
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
4911
|
+
ctx.beginPath();
|
|
4912
|
+
(0, $e41b41d8dcf837ad$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
4913
|
+
ctx.stroke();
|
|
4914
|
+
ctx.restore();
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4424
4917
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
4425
4918
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
4426
4919
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
@@ -4487,7 +4980,41 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4487
4980
|
prevY = fy;
|
|
4488
4981
|
}
|
|
4489
4982
|
}
|
|
4490
|
-
// ── 6b.
|
|
4983
|
+
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
4984
|
+
const energyArchetypes = [
|
|
4985
|
+
"dense-chaotic",
|
|
4986
|
+
"cosmic",
|
|
4987
|
+
"neon-glow",
|
|
4988
|
+
"bold-graphic"
|
|
4989
|
+
];
|
|
4990
|
+
const hasEnergyLines = energyArchetypes.some((a)=>archetype.name.includes(a)) || rng() < 0.25;
|
|
4991
|
+
if (hasEnergyLines && shapePositions.length > 0) {
|
|
4992
|
+
const energyCount = 5 + Math.floor(rng() * 10);
|
|
4993
|
+
ctx.lineCap = "round";
|
|
4994
|
+
for(let e = 0; e < energyCount; e++){
|
|
4995
|
+
// Pick a random shape to radiate from
|
|
4996
|
+
const source = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
4997
|
+
const burstCount = 2 + Math.floor(rng() * 4);
|
|
4998
|
+
const baseAngle = flowAngle(source.x, source.y);
|
|
4999
|
+
for(let b = 0; b < burstCount; b++){
|
|
5000
|
+
const angle = baseAngle + (rng() - 0.5) * 1.2;
|
|
5001
|
+
const lineLen = (source.size * 0.3 + rng() * source.size * 0.5) * scaleFactor * 0.3;
|
|
5002
|
+
const startDist = source.size * 0.5;
|
|
5003
|
+
const sx = source.x + Math.cos(angle) * startDist;
|
|
5004
|
+
const sy = source.y + Math.sin(angle) * startDist;
|
|
5005
|
+
const ex = sx + Math.cos(angle) * lineLen;
|
|
5006
|
+
const ey = sy + Math.sin(angle) * lineLen;
|
|
5007
|
+
ctx.globalAlpha = 0.04 + rng() * 0.06;
|
|
5008
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$90ad0e6170cf6af5)((0, $b5a262d09b87e373$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
5009
|
+
ctx.lineWidth = (0.5 + rng() * 1.5) * scaleFactor;
|
|
5010
|
+
ctx.beginPath();
|
|
5011
|
+
ctx.moveTo(sx, sy);
|
|
5012
|
+
ctx.lineTo(ex, ey);
|
|
5013
|
+
ctx.stroke();
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
4491
5018
|
if (symmetryMode !== "none") {
|
|
4492
5019
|
const canvas = ctx.canvas;
|
|
4493
5020
|
ctx.save();
|
|
@@ -4598,6 +5125,214 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4598
5125
|
ctx.restore();
|
|
4599
5126
|
ctx.globalCompositeOperation = "source-over";
|
|
4600
5127
|
}
|
|
5128
|
+
// 10d. Gradient map — map luminance through a two-color gradient
|
|
5129
|
+
// Uses dominant→accent as the dark→light ramp for a cohesive tonal look
|
|
5130
|
+
if (rng() < 0.35) {
|
|
5131
|
+
const gmDark = colorHierarchy.dominant;
|
|
5132
|
+
const gmLight = colorHierarchy.accent;
|
|
5133
|
+
ctx.globalAlpha = 0.06 + rng() * 0.06; // very subtle: 6-12%
|
|
5134
|
+
ctx.globalCompositeOperation = "color";
|
|
5135
|
+
// Paint a linear gradient from dark color (top) to light color (bottom)
|
|
5136
|
+
const gmGrad = ctx.createLinearGradient(0, 0, 0, height);
|
|
5137
|
+
gmGrad.addColorStop(0, gmDark);
|
|
5138
|
+
gmGrad.addColorStop(1, gmLight);
|
|
5139
|
+
ctx.fillStyle = gmGrad;
|
|
5140
|
+
ctx.fillRect(0, 0, width, height);
|
|
5141
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5142
|
+
}
|
|
5143
|
+
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5144
|
+
{
|
|
5145
|
+
ctx.save();
|
|
5146
|
+
ctx.globalAlpha = 1;
|
|
5147
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5148
|
+
const borderRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 314));
|
|
5149
|
+
const borderPad = Math.min(width, height) * 0.025;
|
|
5150
|
+
const borderColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
5151
|
+
const borderColorSolid = colorHierarchy.accent;
|
|
5152
|
+
const archName = archetype.name;
|
|
5153
|
+
if (archName.includes("geometric") || archName.includes("op-art") || archName.includes("shattered")) {
|
|
5154
|
+
// Clean ruled lines with corner ornaments
|
|
5155
|
+
ctx.strokeStyle = borderColor;
|
|
5156
|
+
ctx.lineWidth = Math.max(1, 1.5 * scaleFactor);
|
|
5157
|
+
ctx.globalAlpha = 0.18 + borderRng() * 0.1;
|
|
5158
|
+
// Outer rule
|
|
5159
|
+
ctx.strokeRect(borderPad, borderPad, width - borderPad * 2, height - borderPad * 2);
|
|
5160
|
+
// Inner rule (thinner, offset)
|
|
5161
|
+
const innerPad = borderPad * 1.8;
|
|
5162
|
+
ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
|
|
5163
|
+
ctx.globalAlpha *= 0.7;
|
|
5164
|
+
ctx.strokeRect(innerPad, innerPad, width - innerPad * 2, height - innerPad * 2);
|
|
5165
|
+
// Corner ornaments — small squares at each corner
|
|
5166
|
+
const ornSize = borderPad * 0.6;
|
|
5167
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(borderColorSolid, 0.12);
|
|
5168
|
+
const corners = [
|
|
5169
|
+
[
|
|
5170
|
+
borderPad,
|
|
5171
|
+
borderPad
|
|
5172
|
+
],
|
|
5173
|
+
[
|
|
5174
|
+
width - borderPad - ornSize,
|
|
5175
|
+
borderPad
|
|
5176
|
+
],
|
|
5177
|
+
[
|
|
5178
|
+
borderPad,
|
|
5179
|
+
height - borderPad - ornSize
|
|
5180
|
+
],
|
|
5181
|
+
[
|
|
5182
|
+
width - borderPad - ornSize,
|
|
5183
|
+
height - borderPad - ornSize
|
|
5184
|
+
]
|
|
5185
|
+
];
|
|
5186
|
+
for (const [cx2, cy2] of corners){
|
|
5187
|
+
ctx.fillRect(cx2, cy2, ornSize, ornSize);
|
|
5188
|
+
// Diagonal cross inside ornament
|
|
5189
|
+
ctx.beginPath();
|
|
5190
|
+
ctx.moveTo(cx2, cy2);
|
|
5191
|
+
ctx.lineTo(cx2 + ornSize, cy2 + ornSize);
|
|
5192
|
+
ctx.moveTo(cx2 + ornSize, cy2);
|
|
5193
|
+
ctx.lineTo(cx2, cy2 + ornSize);
|
|
5194
|
+
ctx.stroke();
|
|
5195
|
+
}
|
|
5196
|
+
} else if (archName.includes("botanical") || archName.includes("organic") || archName.includes("watercolor")) {
|
|
5197
|
+
// Vine tendrils — organic curving lines along edges
|
|
5198
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
5199
|
+
ctx.lineWidth = Math.max(0.8, 1.2 * scaleFactor);
|
|
5200
|
+
ctx.globalAlpha = 0.12 + borderRng() * 0.08;
|
|
5201
|
+
ctx.lineCap = "round";
|
|
5202
|
+
const tendrilCount = 8 + Math.floor(borderRng() * 8);
|
|
5203
|
+
for(let t = 0; t < tendrilCount; t++){
|
|
5204
|
+
// Start from a random edge point
|
|
5205
|
+
const edge = Math.floor(borderRng() * 4);
|
|
5206
|
+
let tx, ty;
|
|
5207
|
+
if (edge === 0) {
|
|
5208
|
+
tx = borderRng() * width;
|
|
5209
|
+
ty = borderPad;
|
|
5210
|
+
} else if (edge === 1) {
|
|
5211
|
+
tx = borderRng() * width;
|
|
5212
|
+
ty = height - borderPad;
|
|
5213
|
+
} else if (edge === 2) {
|
|
5214
|
+
tx = borderPad;
|
|
5215
|
+
ty = borderRng() * height;
|
|
5216
|
+
} else {
|
|
5217
|
+
tx = width - borderPad;
|
|
5218
|
+
ty = borderRng() * height;
|
|
5219
|
+
}
|
|
5220
|
+
ctx.beginPath();
|
|
5221
|
+
ctx.moveTo(tx, ty);
|
|
5222
|
+
const segs = 3 + Math.floor(borderRng() * 4);
|
|
5223
|
+
for(let s = 0; s < segs; s++){
|
|
5224
|
+
const inward = borderPad * (1 + borderRng() * 2);
|
|
5225
|
+
// Curl inward from edge
|
|
5226
|
+
const cpx2 = tx + (borderRng() - 0.5) * borderPad * 4;
|
|
5227
|
+
const cpy2 = ty + (edge < 2 ? edge === 0 ? inward : -inward : 0);
|
|
5228
|
+
const cpx3 = tx + (edge >= 2 ? edge === 2 ? inward : -inward : (borderRng() - 0.5) * borderPad * 3);
|
|
5229
|
+
const cpy3 = ty + (borderRng() - 0.5) * borderPad * 3;
|
|
5230
|
+
tx = cpx3;
|
|
5231
|
+
ty = cpy3;
|
|
5232
|
+
ctx.quadraticCurveTo(cpx2, cpy2, tx, ty);
|
|
5233
|
+
}
|
|
5234
|
+
ctx.stroke();
|
|
5235
|
+
// Small leaf/dot at tendril end
|
|
5236
|
+
if (borderRng() < 0.6) {
|
|
5237
|
+
ctx.beginPath();
|
|
5238
|
+
ctx.arc(tx, ty, borderPad * (0.15 + borderRng() * 0.2), 0, Math.PI * 2);
|
|
5239
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.08);
|
|
5240
|
+
ctx.fill();
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
} else if (archName.includes("celestial") || archName.includes("cosmic") || archName.includes("neon")) {
|
|
5244
|
+
// Star-studded arcs along edges
|
|
5245
|
+
ctx.globalAlpha = 0.1 + borderRng() * 0.08;
|
|
5246
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.accent, 0.2);
|
|
5247
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.accent, 0.12);
|
|
5248
|
+
ctx.lineWidth = Math.max(0.5, 0.7 * scaleFactor);
|
|
5249
|
+
// Subtle arc along top and bottom
|
|
5250
|
+
ctx.beginPath();
|
|
5251
|
+
ctx.arc(cx, -height * 0.3, height * 0.6, 0.3, Math.PI - 0.3);
|
|
5252
|
+
ctx.stroke();
|
|
5253
|
+
ctx.beginPath();
|
|
5254
|
+
ctx.arc(cx, height * 1.3, height * 0.6, Math.PI + 0.3, -0.3);
|
|
5255
|
+
ctx.stroke();
|
|
5256
|
+
// Scatter small stars along the border region
|
|
5257
|
+
const starCount = 15 + Math.floor(borderRng() * 15);
|
|
5258
|
+
for(let s = 0; s < starCount; s++){
|
|
5259
|
+
const edge = Math.floor(borderRng() * 4);
|
|
5260
|
+
let sx, sy;
|
|
5261
|
+
if (edge === 0) {
|
|
5262
|
+
sx = borderRng() * width;
|
|
5263
|
+
sy = borderPad * (0.5 + borderRng());
|
|
5264
|
+
} else if (edge === 1) {
|
|
5265
|
+
sx = borderRng() * width;
|
|
5266
|
+
sy = height - borderPad * (0.5 + borderRng());
|
|
5267
|
+
} else if (edge === 2) {
|
|
5268
|
+
sx = borderPad * (0.5 + borderRng());
|
|
5269
|
+
sy = borderRng() * height;
|
|
5270
|
+
} else {
|
|
5271
|
+
sx = width - borderPad * (0.5 + borderRng());
|
|
5272
|
+
sy = borderRng() * height;
|
|
5273
|
+
}
|
|
5274
|
+
const starR = (1 + borderRng() * 2.5) * scaleFactor;
|
|
5275
|
+
// 4-point star
|
|
5276
|
+
ctx.beginPath();
|
|
5277
|
+
for(let p = 0; p < 8; p++){
|
|
5278
|
+
const a = p / 8 * Math.PI * 2;
|
|
5279
|
+
const r = p % 2 === 0 ? starR : starR * 0.4;
|
|
5280
|
+
const px2 = sx + Math.cos(a) * r;
|
|
5281
|
+
const py2 = sy + Math.sin(a) * r;
|
|
5282
|
+
if (p === 0) ctx.moveTo(px2, py2);
|
|
5283
|
+
else ctx.lineTo(px2, py2);
|
|
5284
|
+
}
|
|
5285
|
+
ctx.closePath();
|
|
5286
|
+
ctx.fill();
|
|
5287
|
+
}
|
|
5288
|
+
} else if (archName.includes("minimal") || archName.includes("monochrome") || archName.includes("stipple")) {
|
|
5289
|
+
// Thin single rule — understated elegance
|
|
5290
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
5291
|
+
ctx.lineWidth = Math.max(0.5, 0.6 * scaleFactor);
|
|
5292
|
+
ctx.globalAlpha = 0.1 + borderRng() * 0.06;
|
|
5293
|
+
ctx.strokeRect(borderPad * 1.5, borderPad * 1.5, width - borderPad * 3, height - borderPad * 3);
|
|
5294
|
+
}
|
|
5295
|
+
// Other archetypes: no border (intentional — not every image needs one)
|
|
5296
|
+
ctx.restore();
|
|
5297
|
+
}
|
|
5298
|
+
// ── 11. Signature mark — unique geometric chop from hash prefix ──
|
|
5299
|
+
{
|
|
5300
|
+
const sigRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 42));
|
|
5301
|
+
const sigSize = Math.min(width, height) * 0.025;
|
|
5302
|
+
// Bottom-right corner with padding
|
|
5303
|
+
const sigX = width - sigSize * 2.5;
|
|
5304
|
+
const sigY = height - sigSize * 2.5;
|
|
5305
|
+
const sigSegments = 4 + Math.floor(sigRng() * 4); // 4-7 segments
|
|
5306
|
+
const sigColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.accent, 0.15);
|
|
5307
|
+
ctx.save();
|
|
5308
|
+
ctx.globalAlpha = 0.12 + sigRng() * 0.08;
|
|
5309
|
+
ctx.translate(sigX, sigY);
|
|
5310
|
+
ctx.strokeStyle = sigColor;
|
|
5311
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.06);
|
|
5312
|
+
ctx.lineWidth = Math.max(0.5, 0.8 * scaleFactor);
|
|
5313
|
+
// Outer ring
|
|
5314
|
+
ctx.beginPath();
|
|
5315
|
+
ctx.arc(0, 0, sigSize, 0, Math.PI * 2);
|
|
5316
|
+
ctx.stroke();
|
|
5317
|
+
ctx.fill();
|
|
5318
|
+
// Inner geometric pattern — unique per hash
|
|
5319
|
+
ctx.beginPath();
|
|
5320
|
+
for(let s = 0; s < sigSegments; s++){
|
|
5321
|
+
const angle1 = sigRng() * Math.PI * 2;
|
|
5322
|
+
const angle2 = sigRng() * Math.PI * 2;
|
|
5323
|
+
const r1 = sigSize * (0.2 + sigRng() * 0.6);
|
|
5324
|
+
const r2 = sigSize * (0.2 + sigRng() * 0.6);
|
|
5325
|
+
ctx.moveTo(Math.cos(angle1) * r1, Math.sin(angle1) * r1);
|
|
5326
|
+
ctx.lineTo(Math.cos(angle2) * r2, Math.sin(angle2) * r2);
|
|
5327
|
+
}
|
|
5328
|
+
ctx.stroke();
|
|
5329
|
+
// Center dot
|
|
5330
|
+
ctx.beginPath();
|
|
5331
|
+
ctx.arc(0, 0, sigSize * 0.12, 0, Math.PI * 2);
|
|
5332
|
+
ctx.fillStyle = sigColor;
|
|
5333
|
+
ctx.fill();
|
|
5334
|
+
ctx.restore();
|
|
5335
|
+
}
|
|
4601
5336
|
ctx.globalAlpha = 1;
|
|
4602
5337
|
}
|
|
4603
5338
|
|