git-hash-art 0.2.0 → 0.4.0
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/ALGORITHM.md +264 -0
- package/CHANGELOG.md +15 -0
- package/dist/browser.js +488 -56
- package/dist/browser.js.map +1 -1
- package/dist/main.js +491 -57
- package/dist/main.js.map +1 -1
- package/dist/module.js +491 -57
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/browser.ts +1 -6
- package/src/lib/canvas/colors.ts +59 -38
- package/src/lib/canvas/draw.ts +134 -3
- package/src/lib/render.ts +483 -47
package/dist/browser.js
CHANGED
|
@@ -8,9 +8,21 @@ import $4wRzV$colorscheme from "color-scheme";
|
|
|
8
8
|
*/ /**
|
|
9
9
|
* Pure rendering logic — environment-agnostic.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Uses only the standard CanvasRenderingContext2D API so it works
|
|
12
|
+
* identically in Node (@napi-rs/canvas) and browsers.
|
|
13
|
+
*
|
|
14
|
+
* Generation pipeline:
|
|
15
|
+
* 1. Background — radial gradient from hash-derived dark palette
|
|
16
|
+
* 1b. Layered background — large faint shapes / subtle pattern for depth
|
|
17
|
+
* 2. Composition mode — hash selects: radial, flow-field, spiral, grid-subdivision, or clustered
|
|
18
|
+
* 3. Focal points + void zones (negative space)
|
|
19
|
+
* 4. Flow field seed values
|
|
20
|
+
* 5. Shape layers — blend modes, render styles, weighted selection,
|
|
21
|
+
* focal-point placement, atmospheric depth, organic edges
|
|
22
|
+
* 5b. Recursive nesting
|
|
23
|
+
* 6. Flow-line pass — tapered brush-stroke curves
|
|
24
|
+
* 7. Noise texture overlay
|
|
25
|
+
* 8. Organic connecting curves
|
|
14
26
|
*/
|
|
15
27
|
// declare module 'color-scheme';
|
|
16
28
|
|
|
@@ -68,46 +80,61 @@ class $616009579e3d72c5$export$da2372f11bc66b3f {
|
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
// ── Color variation modes ───────────────────────────────────────────
|
|
84
|
+
// The hash deterministically selects a variation, producing dramatically
|
|
85
|
+
// different palettes from the same hue.
|
|
86
|
+
const $b5a262d09b87e373$var$COLOR_VARIATIONS = [
|
|
87
|
+
"soft",
|
|
88
|
+
"hard",
|
|
89
|
+
"pastel",
|
|
90
|
+
"light",
|
|
91
|
+
"dark",
|
|
92
|
+
"default"
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* Pick a color variation mode deterministically from a seed.
|
|
96
|
+
*/ function $b5a262d09b87e373$var$pickVariation(seed) {
|
|
97
|
+
return $b5a262d09b87e373$var$COLOR_VARIATIONS[Math.abs(seed) % $b5a262d09b87e373$var$COLOR_VARIATIONS.length];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Scheme type also varies — some hashes get near-monochromatic palettes,
|
|
101
|
+
* others get high-contrast complementary schemes.
|
|
102
|
+
*/ const $b5a262d09b87e373$var$SCHEME_TYPES = [
|
|
103
|
+
"analogic",
|
|
104
|
+
"mono",
|
|
105
|
+
"contrast",
|
|
106
|
+
"triade",
|
|
107
|
+
"tetrade"
|
|
108
|
+
];
|
|
109
|
+
function $b5a262d09b87e373$var$pickSchemeType(seed) {
|
|
110
|
+
return $b5a262d09b87e373$var$SCHEME_TYPES[Math.abs(seed >> 4) % $b5a262d09b87e373$var$SCHEME_TYPES.length];
|
|
81
111
|
}
|
|
82
112
|
class $b5a262d09b87e373$export$ab958c550f521376 {
|
|
83
113
|
constructor(gitHash){
|
|
84
114
|
this.seed = (0, $616009579e3d72c5$export$39a95c82b20fdf81)(gitHash);
|
|
115
|
+
this.rng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 42));
|
|
116
|
+
// Hash-driven variation and scheme type for palette diversity
|
|
117
|
+
this.variation = $b5a262d09b87e373$var$pickVariation(this.seed);
|
|
118
|
+
this.schemeType = $b5a262d09b87e373$var$pickSchemeType(this.seed);
|
|
85
119
|
this.baseScheme = this.generateBaseScheme();
|
|
86
120
|
this.complementaryScheme = this.generateComplementaryScheme();
|
|
87
121
|
this.triadicScheme = this.generateTriadicScheme();
|
|
88
|
-
this.metallic = this.generateMetallicColors();
|
|
89
122
|
}
|
|
90
123
|
generateBaseScheme() {
|
|
91
124
|
const scheme = new (0, $4wRzV$colorscheme)();
|
|
92
|
-
return scheme.from_hue(this.seed % 360).scheme(
|
|
125
|
+
return scheme.from_hue(this.seed % 360).scheme(this.schemeType).variation(this.variation).colors().map((hex)=>`#${hex}`);
|
|
93
126
|
}
|
|
94
127
|
generateComplementaryScheme() {
|
|
95
128
|
const complementaryHue = (this.seed + 180) % 360;
|
|
129
|
+
// Complementary uses a contrasting variation for tension
|
|
130
|
+
const compVariation = this.variation === "soft" ? "hard" : this.variation === "dark" ? "light" : this.variation;
|
|
96
131
|
const scheme = new (0, $4wRzV$colorscheme)();
|
|
97
|
-
return scheme.from_hue(complementaryHue).scheme("mono").variation(
|
|
132
|
+
return scheme.from_hue(complementaryHue).scheme("mono").variation(compVariation).colors().map((hex)=>`#${hex}`);
|
|
98
133
|
}
|
|
99
134
|
generateTriadicScheme() {
|
|
100
135
|
const triadicHue = (this.seed + 120) % 360;
|
|
101
136
|
const scheme = new (0, $4wRzV$colorscheme)();
|
|
102
|
-
return scheme.from_hue(triadicHue).scheme("triade").variation(
|
|
103
|
-
}
|
|
104
|
-
generateMetallicColors() {
|
|
105
|
-
return {
|
|
106
|
-
gold: "#FFD700",
|
|
107
|
-
silver: "#C0C0C0",
|
|
108
|
-
copper: "#B87333",
|
|
109
|
-
bronze: "#CD7F32"
|
|
110
|
-
};
|
|
137
|
+
return scheme.from_hue(triadicHue).scheme("triade").variation(this.variation).colors().map((hex)=>`#${hex}`);
|
|
111
138
|
}
|
|
112
139
|
/**
|
|
113
140
|
* Returns a flat array of hash-derived colors suitable for art generation.
|
|
@@ -165,6 +192,12 @@ function $b5a262d09b87e373$export$59539d800dbe6858(hex, rng, amount = 0.1) {
|
|
|
165
192
|
const jit = ()=>(rng() - 0.5) * 2 * amount * 255;
|
|
166
193
|
return $b5a262d09b87e373$var$rgbToHex(r + jit(), g + jit(), b + jit());
|
|
167
194
|
}
|
|
195
|
+
function $b5a262d09b87e373$export$fb75607d98509d9(hex, amount) {
|
|
196
|
+
const [r, g, b] = $b5a262d09b87e373$var$hexToRgb(hex);
|
|
197
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
198
|
+
const mix = (c)=>c + (gray - c) * amount;
|
|
199
|
+
return $b5a262d09b87e373$var$rgbToHex(mix(r), mix(g), mix(b));
|
|
200
|
+
}
|
|
168
201
|
|
|
169
202
|
|
|
170
203
|
|
|
@@ -952,6 +985,32 @@ const $e41b41d8dcf837ad$export$4ff7fc6f1af248b5 = {
|
|
|
952
985
|
};
|
|
953
986
|
|
|
954
987
|
|
|
988
|
+
const $e0f99502ff383dd8$export$f821c68fe9beaecf = [
|
|
989
|
+
"source-over",
|
|
990
|
+
"screen",
|
|
991
|
+
"multiply",
|
|
992
|
+
"overlay",
|
|
993
|
+
"soft-light",
|
|
994
|
+
"color-dodge",
|
|
995
|
+
"color-burn",
|
|
996
|
+
"lighter"
|
|
997
|
+
];
|
|
998
|
+
function $e0f99502ff383dd8$export$7bb7bff4e26fa06b(rng) {
|
|
999
|
+
if (rng() < 0.4) return "source-over";
|
|
1000
|
+
return $e0f99502ff383dd8$export$f821c68fe9beaecf[1 + Math.floor(rng() * ($e0f99502ff383dd8$export$f821c68fe9beaecf.length - 1))];
|
|
1001
|
+
}
|
|
1002
|
+
const $e0f99502ff383dd8$var$RENDER_STYLES = [
|
|
1003
|
+
"fill-and-stroke",
|
|
1004
|
+
"fill-and-stroke",
|
|
1005
|
+
"fill-only",
|
|
1006
|
+
"stroke-only",
|
|
1007
|
+
"double-stroke",
|
|
1008
|
+
"dashed",
|
|
1009
|
+
"watercolor"
|
|
1010
|
+
];
|
|
1011
|
+
function $e0f99502ff383dd8$export$9fd4e64b2acd410e(rng) {
|
|
1012
|
+
return $e0f99502ff383dd8$var$RENDER_STYLES[Math.floor(rng() * $e0f99502ff383dd8$var$RENDER_STYLES.length)];
|
|
1013
|
+
}
|
|
955
1014
|
function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
956
1015
|
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
957
1016
|
ctx.save();
|
|
@@ -968,8 +1027,71 @@ function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
968
1027
|
}
|
|
969
1028
|
ctx.restore();
|
|
970
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Apply the chosen render style to the current path.
|
|
1032
|
+
*/ function $e0f99502ff383dd8$var$applyRenderStyle(ctx, style, fillColor, strokeColor, strokeWidth, size, rng) {
|
|
1033
|
+
switch(style){
|
|
1034
|
+
case "fill-only":
|
|
1035
|
+
ctx.fill();
|
|
1036
|
+
break;
|
|
1037
|
+
case "stroke-only":
|
|
1038
|
+
ctx.fill(); // transparent fill to define the path
|
|
1039
|
+
ctx.globalAlpha *= 0.3; // ghost fill
|
|
1040
|
+
ctx.fill();
|
|
1041
|
+
ctx.globalAlpha /= 0.3;
|
|
1042
|
+
ctx.stroke();
|
|
1043
|
+
break;
|
|
1044
|
+
case "double-stroke":
|
|
1045
|
+
ctx.fill();
|
|
1046
|
+
// Outer stroke
|
|
1047
|
+
ctx.lineWidth = strokeWidth * 2;
|
|
1048
|
+
ctx.globalAlpha *= 0.5;
|
|
1049
|
+
ctx.stroke();
|
|
1050
|
+
ctx.globalAlpha /= 0.5;
|
|
1051
|
+
// Inner stroke
|
|
1052
|
+
ctx.lineWidth = strokeWidth * 0.5;
|
|
1053
|
+
ctx.strokeStyle = fillColor;
|
|
1054
|
+
ctx.stroke();
|
|
1055
|
+
break;
|
|
1056
|
+
case "dashed":
|
|
1057
|
+
ctx.fill();
|
|
1058
|
+
ctx.setLineDash([
|
|
1059
|
+
size * 0.05,
|
|
1060
|
+
size * 0.03
|
|
1061
|
+
]);
|
|
1062
|
+
ctx.stroke();
|
|
1063
|
+
ctx.setLineDash([]);
|
|
1064
|
+
break;
|
|
1065
|
+
case "watercolor":
|
|
1066
|
+
{
|
|
1067
|
+
// Draw 3-4 slightly offset passes at low opacity for a bleed effect
|
|
1068
|
+
const passes = 3 + (rng ? Math.floor(rng() * 2) : 0);
|
|
1069
|
+
const savedAlpha = ctx.globalAlpha;
|
|
1070
|
+
ctx.globalAlpha = savedAlpha * (0.3 / passes * 2);
|
|
1071
|
+
for(let p = 0; p < passes; p++){
|
|
1072
|
+
const jx = rng ? (rng() - 0.5) * size * 0.06 : 0;
|
|
1073
|
+
const jy = rng ? (rng() - 0.5) * size * 0.06 : 0;
|
|
1074
|
+
ctx.save();
|
|
1075
|
+
ctx.translate(jx, jy);
|
|
1076
|
+
ctx.fill();
|
|
1077
|
+
ctx.restore();
|
|
1078
|
+
}
|
|
1079
|
+
ctx.globalAlpha = savedAlpha;
|
|
1080
|
+
// Light stroke on top
|
|
1081
|
+
ctx.globalAlpha *= 0.4;
|
|
1082
|
+
ctx.stroke();
|
|
1083
|
+
ctx.globalAlpha /= 0.4;
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
case "fill-and-stroke":
|
|
1087
|
+
default:
|
|
1088
|
+
ctx.fill();
|
|
1089
|
+
ctx.stroke();
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
971
1093
|
function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
972
|
-
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 } = config;
|
|
1094
|
+
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;
|
|
973
1095
|
ctx.save();
|
|
974
1096
|
ctx.translate(x, y);
|
|
975
1097
|
ctx.rotate(rotation * Math.PI / 180);
|
|
@@ -992,8 +1114,7 @@ function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
992
1114
|
const drawFunction = (0, $e41b41d8dcf837ad$export$4ff7fc6f1af248b5)[shape];
|
|
993
1115
|
if (drawFunction) {
|
|
994
1116
|
drawFunction(ctx, size);
|
|
995
|
-
ctx
|
|
996
|
-
ctx.stroke();
|
|
1117
|
+
$e0f99502ff383dd8$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
997
1118
|
}
|
|
998
1119
|
// Reset shadow so patterns aren't double-glowed
|
|
999
1120
|
if (glowRadius > 0) ctx.shadowBlur = 0;
|
|
@@ -1025,6 +1146,138 @@ function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1025
1146
|
};
|
|
1026
1147
|
|
|
1027
1148
|
|
|
1149
|
+
// ── Shape categories for weighted selection ─────────────────────────
|
|
1150
|
+
const $1f63dc64b5593c73$var$BASIC_SHAPES = [
|
|
1151
|
+
"circle",
|
|
1152
|
+
"square",
|
|
1153
|
+
"triangle",
|
|
1154
|
+
"hexagon",
|
|
1155
|
+
"diamond",
|
|
1156
|
+
"cube"
|
|
1157
|
+
];
|
|
1158
|
+
const $1f63dc64b5593c73$var$COMPLEX_SHAPES = [
|
|
1159
|
+
"star",
|
|
1160
|
+
"jacked-star",
|
|
1161
|
+
"heart",
|
|
1162
|
+
"platonicSolid",
|
|
1163
|
+
"fibonacciSpiral",
|
|
1164
|
+
"islamicPattern",
|
|
1165
|
+
"celticKnot",
|
|
1166
|
+
"merkaba",
|
|
1167
|
+
"fractal"
|
|
1168
|
+
];
|
|
1169
|
+
const $1f63dc64b5593c73$var$SACRED_SHAPES = [
|
|
1170
|
+
"mandala",
|
|
1171
|
+
"flowerOfLife",
|
|
1172
|
+
"treeOfLife",
|
|
1173
|
+
"metatronsCube",
|
|
1174
|
+
"sriYantra",
|
|
1175
|
+
"seedOfLife",
|
|
1176
|
+
"vesicaPiscis",
|
|
1177
|
+
"torus",
|
|
1178
|
+
"eggOfLife"
|
|
1179
|
+
];
|
|
1180
|
+
const $1f63dc64b5593c73$var$COMPOSITION_MODES = [
|
|
1181
|
+
"radial",
|
|
1182
|
+
"flow-field",
|
|
1183
|
+
"spiral",
|
|
1184
|
+
"grid-subdivision",
|
|
1185
|
+
"clustered"
|
|
1186
|
+
];
|
|
1187
|
+
// ── Helper: pick shape with layer-aware weighting ───────────────────
|
|
1188
|
+
function $1f63dc64b5593c73$var$pickShape(rng, layerRatio, shapeNames) {
|
|
1189
|
+
const basicW = 1 - layerRatio * 0.6;
|
|
1190
|
+
const complexW = 0.3 + layerRatio * 0.3;
|
|
1191
|
+
const sacredW = 0.1 + layerRatio * 0.4;
|
|
1192
|
+
const total = basicW + complexW + sacredW;
|
|
1193
|
+
const roll = rng() * total;
|
|
1194
|
+
let pool;
|
|
1195
|
+
if (roll < basicW) pool = $1f63dc64b5593c73$var$BASIC_SHAPES;
|
|
1196
|
+
else if (roll < basicW + complexW) pool = $1f63dc64b5593c73$var$COMPLEX_SHAPES;
|
|
1197
|
+
else pool = $1f63dc64b5593c73$var$SACRED_SHAPES;
|
|
1198
|
+
const available = pool.filter((s)=>shapeNames.includes(s));
|
|
1199
|
+
if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
1200
|
+
return available[Math.floor(rng() * available.length)];
|
|
1201
|
+
}
|
|
1202
|
+
// ── Helper: get position based on composition mode ──────────────────
|
|
1203
|
+
function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
1204
|
+
switch(mode){
|
|
1205
|
+
case "radial":
|
|
1206
|
+
{
|
|
1207
|
+
const angle = rng() * Math.PI * 2;
|
|
1208
|
+
const maxR = Math.min(width, height) * 0.45;
|
|
1209
|
+
const r = Math.pow(rng(), 0.7) * maxR;
|
|
1210
|
+
return {
|
|
1211
|
+
x: cx + Math.cos(angle) * r,
|
|
1212
|
+
y: cy + Math.sin(angle) * r
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
case "spiral":
|
|
1216
|
+
{
|
|
1217
|
+
const t = shapeIndex / totalShapes;
|
|
1218
|
+
const turns = 3 + rng() * 2;
|
|
1219
|
+
const angle = t * Math.PI * 2 * turns;
|
|
1220
|
+
const maxR = Math.min(width, height) * 0.42;
|
|
1221
|
+
const r = t * maxR + (rng() - 0.5) * maxR * 0.15;
|
|
1222
|
+
return {
|
|
1223
|
+
x: cx + Math.cos(angle) * r,
|
|
1224
|
+
y: cy + Math.sin(angle) * r
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
case "grid-subdivision":
|
|
1228
|
+
{
|
|
1229
|
+
const cells = 3 + Math.floor(rng() * 3);
|
|
1230
|
+
const cellW = width / cells;
|
|
1231
|
+
const cellH = height / cells;
|
|
1232
|
+
const gx = Math.floor(rng() * cells);
|
|
1233
|
+
const gy = Math.floor(rng() * cells);
|
|
1234
|
+
return {
|
|
1235
|
+
x: gx * cellW + rng() * cellW,
|
|
1236
|
+
y: gy * cellH + rng() * cellH
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
case "clustered":
|
|
1240
|
+
{
|
|
1241
|
+
const numClusters = 3 + Math.floor(rng() * 3);
|
|
1242
|
+
const ci = Math.floor(rng() * numClusters);
|
|
1243
|
+
const clusterRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(String(ci), 999));
|
|
1244
|
+
const clx = width * (0.15 + clusterRng() * 0.7);
|
|
1245
|
+
const cly = height * (0.15 + clusterRng() * 0.7);
|
|
1246
|
+
const spread = Math.min(width, height) * 0.18;
|
|
1247
|
+
return {
|
|
1248
|
+
x: clx + (rng() - 0.5) * spread * 2,
|
|
1249
|
+
y: cly + (rng() - 0.5) * spread * 2
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
case "flow-field":
|
|
1253
|
+
default:
|
|
1254
|
+
return {
|
|
1255
|
+
x: rng() * width,
|
|
1256
|
+
y: rng() * height
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
// ── Helper: positional color blending ───────────────────────────────
|
|
1261
|
+
function $1f63dc64b5593c73$var$getPositionalColor(x, y, width, height, colors, rng) {
|
|
1262
|
+
const nx = x / width;
|
|
1263
|
+
const ny = y / height;
|
|
1264
|
+
const posIndex = (nx * 0.6 + ny * 0.4) * (colors.length - 1);
|
|
1265
|
+
const baseIdx = Math.floor(posIndex) % colors.length;
|
|
1266
|
+
return (0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[baseIdx], rng, 0.08);
|
|
1267
|
+
}
|
|
1268
|
+
// ── Helper: check if a position is inside a void zone (Feature E) ───
|
|
1269
|
+
function $1f63dc64b5593c73$var$isInVoidZone(x, y, voidZones) {
|
|
1270
|
+
for (const zone of voidZones){
|
|
1271
|
+
if (Math.hypot(x - zone.x, y - zone.y) < zone.radius) return true;
|
|
1272
|
+
}
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
// ── Helper: density check for negative space (Feature E) ────────────
|
|
1276
|
+
function $1f63dc64b5593c73$var$localDensity(x, y, positions, radius) {
|
|
1277
|
+
let count = 0;
|
|
1278
|
+
for (const p of positions)if (Math.hypot(x - p.x, y - p.y) < radius) count++;
|
|
1279
|
+
return count;
|
|
1280
|
+
}
|
|
1028
1281
|
function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
1029
1282
|
const finalConfig = {
|
|
1030
1283
|
...(0, $81c1b644006d48ec$export$c2f8e0cc249a8d8f),
|
|
@@ -1032,61 +1285,240 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1032
1285
|
};
|
|
1033
1286
|
const { width: width, height: height, gridSize: gridSize, layers: layers, minShapeSize: minShapeSize, maxShapeSize: maxShapeSize, baseOpacity: baseOpacity, opacityReduction: opacityReduction } = finalConfig;
|
|
1034
1287
|
finalConfig.shapesPerLayer = finalConfig.shapesPerLayer || Math.floor(gridSize * gridSize * 1.5);
|
|
1035
|
-
// --- Color scheme derived from hash ---
|
|
1036
1288
|
const colorScheme = new (0, $b5a262d09b87e373$export$ab958c550f521376)(gitHash);
|
|
1037
1289
|
const colors = colorScheme.getColors();
|
|
1038
1290
|
const [bgStart, bgEnd] = colorScheme.getBackgroundColors();
|
|
1039
|
-
// --- Radial gradient background for depth ---
|
|
1040
|
-
const cx = width / 2;
|
|
1041
|
-
const cy = height / 2;
|
|
1042
|
-
const bgRadius = Math.hypot(cx, cy);
|
|
1043
|
-
const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, bgRadius);
|
|
1044
|
-
gradient.addColorStop(0, bgStart);
|
|
1045
|
-
gradient.addColorStop(1, bgEnd);
|
|
1046
|
-
ctx.fillStyle = gradient;
|
|
1047
|
-
ctx.fillRect(0, 0, width, height);
|
|
1048
1291
|
const shapeNames = Object.keys((0, $e41b41d8dcf837ad$export$4ff7fc6f1af248b5));
|
|
1049
1292
|
const scaleFactor = Math.min(width, height) / 1024;
|
|
1050
1293
|
const adjustedMinSize = minShapeSize * scaleFactor;
|
|
1051
1294
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
1052
|
-
// One master RNG seeded from the full hash — all randomness flows from here
|
|
1053
1295
|
const rng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash));
|
|
1054
|
-
|
|
1296
|
+
const cx = width / 2;
|
|
1297
|
+
const cy = height / 2;
|
|
1298
|
+
// ── 1. Background ──────────────────────────────────────────────
|
|
1299
|
+
const bgRadius = Math.hypot(cx, cy);
|
|
1300
|
+
const bgGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, bgRadius);
|
|
1301
|
+
bgGrad.addColorStop(0, bgStart);
|
|
1302
|
+
bgGrad.addColorStop(1, bgEnd);
|
|
1303
|
+
ctx.fillStyle = bgGrad;
|
|
1304
|
+
ctx.fillRect(0, 0, width, height);
|
|
1305
|
+
// ── 1b. Layered background (Feature G) ─────────────────────────
|
|
1306
|
+
// Draw large, very faint shapes to give the background texture
|
|
1307
|
+
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
1308
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
1309
|
+
for(let i = 0; i < bgShapeCount; i++){
|
|
1310
|
+
const bx = rng() * width;
|
|
1311
|
+
const by = rng() * height;
|
|
1312
|
+
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
1313
|
+
const bColor = colors[Math.floor(rng() * colors.length)];
|
|
1314
|
+
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
1315
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(bColor, 0.15);
|
|
1316
|
+
ctx.beginPath();
|
|
1317
|
+
ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
1318
|
+
ctx.fill();
|
|
1319
|
+
}
|
|
1320
|
+
// Subtle concentric rings from center
|
|
1321
|
+
const ringCount = 2 + Math.floor(rng() * 3);
|
|
1322
|
+
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
1323
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[0], 0.1);
|
|
1324
|
+
ctx.lineWidth = 1 * scaleFactor;
|
|
1325
|
+
for(let i = 1; i <= ringCount; i++){
|
|
1326
|
+
const r = Math.min(width, height) * 0.15 * i;
|
|
1327
|
+
ctx.beginPath();
|
|
1328
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
1329
|
+
ctx.stroke();
|
|
1330
|
+
}
|
|
1331
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1332
|
+
// ── 2. Composition mode ────────────────────────────────────────
|
|
1333
|
+
const compositionMode = $1f63dc64b5593c73$var$COMPOSITION_MODES[Math.floor(rng() * $1f63dc64b5593c73$var$COMPOSITION_MODES.length)];
|
|
1334
|
+
// ── 3. Focal points + void zones ───────────────────────────────
|
|
1335
|
+
const numFocal = 1 + Math.floor(rng() * 2);
|
|
1336
|
+
const focalPoints = [];
|
|
1337
|
+
for(let f = 0; f < numFocal; f++)focalPoints.push({
|
|
1338
|
+
x: width * (0.2 + rng() * 0.6),
|
|
1339
|
+
y: height * (0.2 + rng() * 0.6),
|
|
1340
|
+
strength: 0.3 + rng() * 0.4
|
|
1341
|
+
});
|
|
1342
|
+
// Feature E: 1-2 void zones where shapes are sparse (negative space)
|
|
1343
|
+
const numVoids = Math.floor(rng() * 2) + 1;
|
|
1344
|
+
const voidZones = [];
|
|
1345
|
+
for(let v = 0; v < numVoids; v++)voidZones.push({
|
|
1346
|
+
x: width * (0.15 + rng() * 0.7),
|
|
1347
|
+
y: height * (0.15 + rng() * 0.7),
|
|
1348
|
+
radius: Math.min(width, height) * (0.06 + rng() * 0.1)
|
|
1349
|
+
});
|
|
1350
|
+
function applyFocalBias(rx, ry) {
|
|
1351
|
+
let nearest = focalPoints[0];
|
|
1352
|
+
let minDist = Infinity;
|
|
1353
|
+
for (const fp of focalPoints){
|
|
1354
|
+
const d = Math.hypot(rx - fp.x, ry - fp.y);
|
|
1355
|
+
if (d < minDist) {
|
|
1356
|
+
minDist = d;
|
|
1357
|
+
nearest = fp;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const pull = nearest.strength * rng() * 0.5;
|
|
1361
|
+
return [
|
|
1362
|
+
rx + (nearest.x - rx) * pull,
|
|
1363
|
+
ry + (nearest.y - ry) * pull
|
|
1364
|
+
];
|
|
1365
|
+
}
|
|
1366
|
+
// ── 4. Flow field seed values ──────────────────────────────────
|
|
1367
|
+
const fieldAngleBase = rng() * Math.PI * 2;
|
|
1368
|
+
const fieldFreq = 0.5 + rng() * 2;
|
|
1369
|
+
function flowAngle(x, y) {
|
|
1370
|
+
return fieldAngleBase + Math.sin(x / width * fieldFreq * Math.PI * 2) * Math.PI * 0.5 + Math.cos(y / height * fieldFreq * Math.PI * 2) * Math.PI * 0.5;
|
|
1371
|
+
}
|
|
1372
|
+
// ── 5. Shape layers ────────────────────────────────────────────
|
|
1055
1373
|
const shapePositions = [];
|
|
1374
|
+
const densityCheckRadius = Math.min(width, height) * 0.08;
|
|
1375
|
+
const maxLocalDensity = Math.ceil(finalConfig.shapesPerLayer * 0.15);
|
|
1056
1376
|
for(let layer = 0; layer < layers; layer++){
|
|
1377
|
+
const layerRatio = layers > 1 ? layer / (layers - 1) : 0;
|
|
1057
1378
|
const numShapes = finalConfig.shapesPerLayer + Math.floor(rng() * finalConfig.shapesPerLayer * 0.3);
|
|
1058
|
-
// Layer opacity decays gently so all layers remain visible
|
|
1059
1379
|
const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);
|
|
1060
|
-
// Later layers use smaller shapes for depth
|
|
1061
1380
|
const layerSizeScale = 1 - layer * 0.15;
|
|
1381
|
+
// Feature B: per-layer blend mode
|
|
1382
|
+
const layerBlend = (0, $e0f99502ff383dd8$export$7bb7bff4e26fa06b)(rng);
|
|
1383
|
+
ctx.globalCompositeOperation = layerBlend;
|
|
1384
|
+
// Feature C: per-layer render style bias
|
|
1385
|
+
const layerRenderStyle = (0, $e0f99502ff383dd8$export$9fd4e64b2acd410e)(rng);
|
|
1386
|
+
// Feature D: atmospheric desaturation for later layers
|
|
1387
|
+
const atmosphericDesat = layerRatio * 0.3; // 0 for first layer, up to 0.3 for last
|
|
1062
1388
|
for(let i = 0; i < numShapes; i++){
|
|
1063
|
-
|
|
1064
|
-
const
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1389
|
+
// Position from composition mode, then focal bias
|
|
1390
|
+
const rawPos = $1f63dc64b5593c73$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
1391
|
+
const [x, y] = applyFocalBias(rawPos.x, rawPos.y);
|
|
1392
|
+
// Feature E: skip shapes in void zones, reduce in dense areas
|
|
1393
|
+
if ($1f63dc64b5593c73$var$isInVoidZone(x, y, voidZones)) {
|
|
1394
|
+
// 85% chance to skip — allows a few shapes to bleed in
|
|
1395
|
+
if (rng() < 0.85) continue;
|
|
1396
|
+
}
|
|
1397
|
+
if ($1f63dc64b5593c73$var$localDensity(x, y, shapePositions, densityCheckRadius) > maxLocalDensity) {
|
|
1398
|
+
if (rng() < 0.6) continue; // thin out dense areas
|
|
1399
|
+
}
|
|
1400
|
+
// Weighted shape selection
|
|
1401
|
+
const shape = $1f63dc64b5593c73$var$pickShape(rng, layerRatio, shapeNames);
|
|
1402
|
+
// Power distribution for size
|
|
1068
1403
|
const sizeT = Math.pow(rng(), 1.8);
|
|
1069
1404
|
const size = (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) * layerSizeScale;
|
|
1070
|
-
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1405
|
+
// Flow-field rotation in flow-field mode, random otherwise
|
|
1406
|
+
const rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
|
|
1407
|
+
// Positional color blending + jitter
|
|
1408
|
+
let fillBase = $1f63dc64b5593c73$var$getPositionalColor(x, y, width, height, colors, rng);
|
|
1409
|
+
const strokeBase = colors[Math.floor(rng() * colors.length)];
|
|
1410
|
+
// Feature D: desaturate colors on later layers for depth
|
|
1411
|
+
if (atmosphericDesat > 0) fillBase = (0, $b5a262d09b87e373$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
1412
|
+
const fillColor = (0, $b5a262d09b87e373$export$59539d800dbe6858)(fillBase, rng, 0.06);
|
|
1413
|
+
const strokeColor = (0, $b5a262d09b87e373$export$59539d800dbe6858)(strokeBase, rng, 0.05);
|
|
1414
|
+
// Semi-transparent fill
|
|
1415
|
+
const fillAlpha = 0.2 + rng() * 0.5;
|
|
1416
|
+
const transparentFill = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(fillColor, fillAlpha);
|
|
1073
1417
|
const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor;
|
|
1074
1418
|
ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5);
|
|
1419
|
+
// Glow on sacred shapes more often
|
|
1420
|
+
const isSacred = $1f63dc64b5593c73$var$SACRED_SHAPES.includes(shape);
|
|
1421
|
+
const glowChance = isSacred ? 0.45 : 0.2;
|
|
1422
|
+
const hasGlow = rng() < glowChance;
|
|
1423
|
+
const glowRadius = hasGlow ? (8 + rng() * 20) * scaleFactor : 0;
|
|
1424
|
+
// Gradient fill on ~30%
|
|
1425
|
+
const hasGradient = rng() < 0.3;
|
|
1426
|
+
const gradientEnd = hasGradient ? (0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1) : undefined;
|
|
1427
|
+
// Feature C: per-shape render style (70% use layer style, 30% pick their own)
|
|
1428
|
+
const shapeRenderStyle = rng() < 0.7 ? layerRenderStyle : (0, $e0f99502ff383dd8$export$9fd4e64b2acd410e)(rng);
|
|
1429
|
+
// Feature F: organic edge jitter — applied via watercolor style on ~15% of shapes
|
|
1430
|
+
const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
|
|
1431
|
+
const finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
|
|
1075
1432
|
(0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
|
|
1076
|
-
fillColor:
|
|
1433
|
+
fillColor: transparentFill,
|
|
1077
1434
|
strokeColor: strokeColor,
|
|
1078
1435
|
strokeWidth: strokeWidth,
|
|
1079
1436
|
size: size,
|
|
1080
1437
|
rotation: rotation,
|
|
1081
|
-
proportionType: "GOLDEN_RATIO"
|
|
1438
|
+
proportionType: "GOLDEN_RATIO",
|
|
1439
|
+
glowRadius: glowRadius,
|
|
1440
|
+
glowColor: hasGlow ? (0, $b5a262d09b87e373$export$f2121afcad3d553f)(fillColor, 0.6) : undefined,
|
|
1441
|
+
gradientFillEnd: gradientEnd,
|
|
1442
|
+
renderStyle: finalRenderStyle,
|
|
1443
|
+
rng: rng
|
|
1082
1444
|
});
|
|
1083
1445
|
shapePositions.push({
|
|
1084
1446
|
x: x,
|
|
1085
|
-
y: y
|
|
1447
|
+
y: y,
|
|
1448
|
+
size: size
|
|
1086
1449
|
});
|
|
1450
|
+
// ── 5b. Recursive nesting ──────────────────────────────────
|
|
1451
|
+
if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
|
|
1452
|
+
const innerCount = 1 + Math.floor(rng() * 3);
|
|
1453
|
+
for(let n = 0; n < innerCount; n++){
|
|
1454
|
+
const innerShape = $1f63dc64b5593c73$var$pickShape(rng, Math.min(1, layerRatio + 0.3), shapeNames);
|
|
1455
|
+
const innerSize = size * (0.15 + rng() * 0.25);
|
|
1456
|
+
const innerOffX = (rng() - 0.5) * size * 0.4;
|
|
1457
|
+
const innerOffY = (rng() - 0.5) * size * 0.4;
|
|
1458
|
+
const innerRot = rng() * 360;
|
|
1459
|
+
const innerFill = (0, $b5a262d09b87e373$export$f2121afcad3d553f)((0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1), 0.3 + rng() * 0.4);
|
|
1460
|
+
ctx.globalAlpha = layerOpacity * 0.7;
|
|
1461
|
+
(0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, innerShape, x + innerOffX, y + innerOffY, {
|
|
1462
|
+
fillColor: innerFill,
|
|
1463
|
+
strokeColor: (0, $b5a262d09b87e373$export$f2121afcad3d553f)(strokeColor, 0.5),
|
|
1464
|
+
strokeWidth: strokeWidth * 0.6,
|
|
1465
|
+
size: innerSize,
|
|
1466
|
+
rotation: innerRot,
|
|
1467
|
+
proportionType: "GOLDEN_RATIO",
|
|
1468
|
+
renderStyle: shapeRenderStyle,
|
|
1469
|
+
rng: rng
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
// Reset blend mode for post-processing passes
|
|
1476
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1477
|
+
// ── 6. Flow-line pass (Feature H: tapered brush strokes) ───────
|
|
1478
|
+
const numFlowLines = 6 + Math.floor(rng() * 10);
|
|
1479
|
+
for(let i = 0; i < numFlowLines; i++){
|
|
1480
|
+
let fx = rng() * width;
|
|
1481
|
+
let fy = rng() * height;
|
|
1482
|
+
const steps = 30 + Math.floor(rng() * 40);
|
|
1483
|
+
const stepLen = (3 + rng() * 5) * scaleFactor;
|
|
1484
|
+
const startWidth = (1 + rng() * 3) * scaleFactor;
|
|
1485
|
+
const lineColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.4);
|
|
1486
|
+
const lineAlpha = 0.06 + rng() * 0.1;
|
|
1487
|
+
// Draw as individual segments with tapering width
|
|
1488
|
+
let prevX = fx;
|
|
1489
|
+
let prevY = fy;
|
|
1490
|
+
for(let s = 0; s < steps; s++){
|
|
1491
|
+
const angle = flowAngle(fx, fy) + (rng() - 0.5) * 0.3;
|
|
1492
|
+
fx += Math.cos(angle) * stepLen;
|
|
1493
|
+
fy += Math.sin(angle) * stepLen;
|
|
1494
|
+
if (fx < 0 || fx > width || fy < 0 || fy > height) break;
|
|
1495
|
+
// Taper: thick at start, thin at end
|
|
1496
|
+
const taper = 1 - s / steps * 0.8;
|
|
1497
|
+
ctx.globalAlpha = lineAlpha * taper;
|
|
1498
|
+
ctx.strokeStyle = lineColor;
|
|
1499
|
+
ctx.lineWidth = startWidth * taper;
|
|
1500
|
+
ctx.lineCap = "round";
|
|
1501
|
+
ctx.beginPath();
|
|
1502
|
+
ctx.moveTo(prevX, prevY);
|
|
1503
|
+
ctx.lineTo(fx, fy);
|
|
1504
|
+
ctx.stroke();
|
|
1505
|
+
prevX = fx;
|
|
1506
|
+
prevY = fy;
|
|
1087
1507
|
}
|
|
1088
1508
|
}
|
|
1089
|
-
//
|
|
1509
|
+
// ── 7. Noise texture overlay ───────────────────────────────────
|
|
1510
|
+
const noiseRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 777));
|
|
1511
|
+
const noiseDensity = Math.floor(width * height / 800);
|
|
1512
|
+
for(let i = 0; i < noiseDensity; i++){
|
|
1513
|
+
const nx = noiseRng() * width;
|
|
1514
|
+
const ny = noiseRng() * height;
|
|
1515
|
+
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
1516
|
+
const alpha = 0.01 + noiseRng() * 0.03;
|
|
1517
|
+
ctx.globalAlpha = alpha;
|
|
1518
|
+
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
1519
|
+
ctx.fillRect(nx, ny, 1 * scaleFactor, 1 * scaleFactor);
|
|
1520
|
+
}
|
|
1521
|
+
// ── 8. Organic connecting curves ───────────────────────────────
|
|
1090
1522
|
if (shapePositions.length > 1) {
|
|
1091
1523
|
const numCurves = Math.floor(8 * (width * height) / 1048576);
|
|
1092
1524
|
ctx.lineWidth = 0.8 * scaleFactor;
|
|
@@ -1104,8 +1536,8 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1104
1536
|
const bulge = (rng() - 0.5) * dist * 0.4;
|
|
1105
1537
|
const cpx = mx + -dy / (dist || 1) * bulge;
|
|
1106
1538
|
const cpy = my + dx / (dist || 1) * bulge;
|
|
1107
|
-
ctx.globalAlpha = 0.
|
|
1108
|
-
ctx.strokeStyle = colors[Math.floor(rng() * colors.length)];
|
|
1539
|
+
ctx.globalAlpha = 0.06 + rng() * 0.1;
|
|
1540
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.3);
|
|
1109
1541
|
ctx.beginPath();
|
|
1110
1542
|
ctx.moveTo(a.x, a.y);
|
|
1111
1543
|
ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);
|