git-hash-art 0.10.1 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-hash-art",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "author": "gfargo <ghfargo@gmail.com>",
5
5
  "scripts": {
6
6
  "watch": "parcel watch",
@@ -34,6 +34,14 @@ export type PaletteMode =
34
34
 
35
35
  // ── Archetype definition ────────────────────────────────────────────
36
36
 
37
+ export type CompositionMode =
38
+ | "radial"
39
+ | "flow-field"
40
+ | "spiral"
41
+ | "grid-subdivision"
42
+ | "clustered"
43
+ | "golden-spiral";
44
+
37
45
  export interface Archetype {
38
46
  name: string;
39
47
  /** Override gridSize (controls shape count) */
@@ -54,6 +62,8 @@ export interface Archetype {
54
62
  paletteMode: PaletteMode;
55
63
  /** Preferred render styles (weighted toward these) */
56
64
  preferredStyles: RenderStyle[];
65
+ /** Preferred composition modes (70% chance of using one of these) */
66
+ preferredCompositions: CompositionMode[];
57
67
  /** Flow line count multiplier (1 = default) */
58
68
  flowLineMultiplier: number;
59
69
  /** Whether to draw the hero shape */
@@ -80,6 +90,7 @@ const ARCHETYPES: Archetype[] = [
80
90
  backgroundStyle: "radial-dark",
81
91
  paletteMode: "harmonious",
82
92
  preferredStyles: ["fill-and-stroke", "watercolor", "fill-only"],
93
+ preferredCompositions: ["clustered", "flow-field", "radial"],
83
94
  flowLineMultiplier: 2.5,
84
95
  heroShape: false,
85
96
  glowMultiplier: 0.5,
@@ -97,6 +108,7 @@ const ARCHETYPES: Archetype[] = [
97
108
  backgroundStyle: "solid-light",
98
109
  paletteMode: "duotone",
99
110
  preferredStyles: ["fill-and-stroke", "stroke-only", "incomplete"],
111
+ preferredCompositions: ["golden-spiral", "grid-subdivision"],
100
112
  flowLineMultiplier: 0.3,
101
113
  heroShape: true,
102
114
  glowMultiplier: 0,
@@ -114,6 +126,7 @@ const ARCHETYPES: Archetype[] = [
114
126
  backgroundStyle: "radial-dark",
115
127
  paletteMode: "earth",
116
128
  preferredStyles: ["watercolor", "fill-only", "incomplete"],
129
+ preferredCompositions: ["flow-field", "golden-spiral", "spiral"],
117
130
  flowLineMultiplier: 4,
118
131
  heroShape: false,
119
132
  glowMultiplier: 0.3,
@@ -131,6 +144,7 @@ const ARCHETYPES: Archetype[] = [
131
144
  backgroundStyle: "solid-dark",
132
145
  paletteMode: "high-contrast",
133
146
  preferredStyles: ["stroke-only", "dashed", "double-stroke", "hatched"],
147
+ preferredCompositions: ["grid-subdivision", "radial"],
134
148
  flowLineMultiplier: 0,
135
149
  heroShape: false,
136
150
  glowMultiplier: 0,
@@ -148,6 +162,7 @@ const ARCHETYPES: Archetype[] = [
148
162
  backgroundStyle: "radial-light",
149
163
  paletteMode: "pastel-light",
150
164
  preferredStyles: ["watercolor", "incomplete", "fill-only"],
165
+ preferredCompositions: ["golden-spiral", "radial", "spiral"],
151
166
  flowLineMultiplier: 1.5,
152
167
  heroShape: true,
153
168
  glowMultiplier: 2,
@@ -165,6 +180,7 @@ const ARCHETYPES: Archetype[] = [
165
180
  backgroundStyle: "linear-diagonal",
166
181
  paletteMode: "duotone",
167
182
  preferredStyles: ["fill-and-stroke", "double-stroke"],
183
+ preferredCompositions: ["grid-subdivision", "golden-spiral"],
168
184
  flowLineMultiplier: 0,
169
185
  heroShape: true,
170
186
  glowMultiplier: 0,
@@ -182,6 +198,7 @@ const ARCHETYPES: Archetype[] = [
182
198
  backgroundStyle: "solid-dark",
183
199
  paletteMode: "neon",
184
200
  preferredStyles: ["stroke-only", "double-stroke", "dashed"],
201
+ preferredCompositions: ["radial", "spiral", "clustered"],
185
202
  flowLineMultiplier: 2,
186
203
  heroShape: true,
187
204
  glowMultiplier: 3,
@@ -199,6 +216,7 @@ const ARCHETYPES: Archetype[] = [
199
216
  backgroundStyle: "solid-light",
200
217
  paletteMode: "monochrome",
201
218
  preferredStyles: ["hatched", "incomplete", "stroke-only", "dashed"],
219
+ preferredCompositions: ["flow-field", "grid-subdivision", "clustered"],
202
220
  flowLineMultiplier: 1.5,
203
221
  heroShape: false,
204
222
  glowMultiplier: 0,
@@ -216,6 +234,7 @@ const ARCHETYPES: Archetype[] = [
216
234
  backgroundStyle: "radial-dark",
217
235
  paletteMode: "neon",
218
236
  preferredStyles: ["fill-only", "watercolor", "fill-and-stroke"],
237
+ preferredCompositions: ["radial", "spiral", "golden-spiral"],
219
238
  flowLineMultiplier: 3,
220
239
  heroShape: true,
221
240
  glowMultiplier: 2.5,
@@ -233,6 +252,7 @@ const ARCHETYPES: Archetype[] = [
233
252
  backgroundStyle: "radial-light",
234
253
  paletteMode: "harmonious",
235
254
  preferredStyles: ["watercolor", "fill-only", "incomplete"],
255
+ preferredCompositions: ["golden-spiral", "flow-field", "radial"],
236
256
  flowLineMultiplier: 0.5,
237
257
  heroShape: false,
238
258
  glowMultiplier: 0.3,
@@ -250,6 +270,7 @@ const ARCHETYPES: Archetype[] = [
250
270
  backgroundStyle: "solid-light",
251
271
  paletteMode: "high-contrast",
252
272
  preferredStyles: ["fill-and-stroke", "stroke-only", "dashed"],
273
+ preferredCompositions: ["grid-subdivision", "radial"],
253
274
  flowLineMultiplier: 0,
254
275
  heroShape: false,
255
276
  glowMultiplier: 0,
@@ -267,6 +288,7 @@ const ARCHETYPES: Archetype[] = [
267
288
  backgroundStyle: "solid-light",
268
289
  paletteMode: "duotone",
269
290
  preferredStyles: ["fill-and-stroke", "fill-only", "double-stroke"],
291
+ preferredCompositions: ["grid-subdivision", "clustered"],
270
292
  flowLineMultiplier: 0,
271
293
  heroShape: true,
272
294
  glowMultiplier: 0,
@@ -284,6 +306,7 @@ const ARCHETYPES: Archetype[] = [
284
306
  backgroundStyle: "radial-dark",
285
307
  paletteMode: "harmonious",
286
308
  preferredStyles: ["fill-and-stroke", "watercolor", "fill-only"],
309
+ preferredCompositions: ["radial", "golden-spiral", "flow-field"],
287
310
  flowLineMultiplier: 1,
288
311
  heroShape: true,
289
312
  glowMultiplier: 1,
@@ -301,6 +324,7 @@ const ARCHETYPES: Archetype[] = [
301
324
  backgroundStyle: "solid-dark",
302
325
  paletteMode: "high-contrast",
303
326
  preferredStyles: ["fill-and-stroke", "stroke-only", "fill-only"],
327
+ preferredCompositions: ["clustered", "grid-subdivision", "radial"],
304
328
  flowLineMultiplier: 0,
305
329
  heroShape: false,
306
330
  glowMultiplier: 0.3,
@@ -318,6 +342,7 @@ const ARCHETYPES: Archetype[] = [
318
342
  backgroundStyle: "radial-light",
319
343
  paletteMode: "earth",
320
344
  preferredStyles: ["watercolor", "fill-only", "incomplete"],
345
+ preferredCompositions: ["flow-field", "golden-spiral", "spiral"],
321
346
  flowLineMultiplier: 3,
322
347
  heroShape: true,
323
348
  glowMultiplier: 0.2,
@@ -335,6 +360,7 @@ const ARCHETYPES: Archetype[] = [
335
360
  backgroundStyle: "solid-light",
336
361
  paletteMode: "monochrome",
337
362
  preferredStyles: ["stipple", "fill-only", "hatched"],
363
+ preferredCompositions: ["radial", "clustered", "flow-field"],
338
364
  flowLineMultiplier: 0,
339
365
  heroShape: false,
340
366
  glowMultiplier: 0,
@@ -352,6 +378,7 @@ const ARCHETYPES: Archetype[] = [
352
378
  backgroundStyle: "radial-dark",
353
379
  paletteMode: "neon",
354
380
  preferredStyles: ["fill-only", "watercolor", "stroke-only", "incomplete"],
381
+ preferredCompositions: ["spiral", "radial", "golden-spiral"],
355
382
  flowLineMultiplier: 2,
356
383
  heroShape: true,
357
384
  glowMultiplier: 2.5,
@@ -374,6 +401,7 @@ function lerpNum(a: number, b: number, t: number): number {
374
401
  function blendArchetypes(a: Archetype, b: Archetype, t: number): Archetype {
375
402
  // Merge preferred styles — unique union, primary archetype first
376
403
  const mergedStyles = [...new Set([...a.preferredStyles, ...b.preferredStyles])] as RenderStyle[];
404
+ const mergedCompositions = [...new Set([...a.preferredCompositions, ...b.preferredCompositions])] as CompositionMode[];
377
405
 
378
406
  return {
379
407
  name: `${a.name}+${b.name}`,
@@ -386,6 +414,7 @@ function blendArchetypes(a: Archetype, b: Archetype, t: number): Archetype {
386
414
  backgroundStyle: t < 0.5 ? a.backgroundStyle : b.backgroundStyle,
387
415
  paletteMode: t < 0.5 ? a.paletteMode : b.paletteMode,
388
416
  preferredStyles: mergedStyles,
417
+ preferredCompositions: mergedCompositions,
389
418
  flowLineMultiplier: lerpNum(a.flowLineMultiplier, b.flowLineMultiplier, t),
390
419
  heroShape: t < 0.5 ? a.heroShape : b.heroShape,
391
420
  glowMultiplier: lerpNum(a.glowMultiplier, b.glowMultiplier, t),
@@ -388,14 +388,16 @@ export function buildColorHierarchy(colors: string[], rng: () => number): ColorH
388
388
  all: colors,
389
389
  };
390
390
  }
391
- // Pick dominant as the color closest to the palette's average hue
391
+ // Pick dominant as the color with the highest chroma (saturation × distance from gray)
392
+ // This selects the most visually prominent color rather than the average
392
393
  const hsls = colors.map((c) => hexToHsl(c));
393
- const avgHue = hsls.reduce((s, h) => s + h[0], 0) / hsls.length;
394
394
  let dominantIdx = 0;
395
- let minDist = 360;
395
+ let maxChroma = -1;
396
396
  for (let i = 0; i < hsls.length; i++) {
397
- const d = Math.min(Math.abs(hsls[i][0] - avgHue), 360 - Math.abs(hsls[i][0] - avgHue));
398
- if (d < minDist) { minDist = d; dominantIdx = i; }
397
+ // Chroma approximation: saturation × how far lightness is from 50% (gray)
398
+ const lightnessVibrancy = 1 - Math.abs(hsls[i][2] - 0.5) * 2; // peaks at L=0.5
399
+ const chroma = hsls[i][1] * lightnessVibrancy;
400
+ if (chroma > maxChroma) { maxChroma = chroma; dominantIdx = i; }
399
401
  }
400
402
  // Accent is the color most distant from dominant in hue
401
403
  let accentIdx = 0;
@@ -668,16 +668,26 @@ export function enhanceShapeGeneration(
668
668
  ctx.shadowOffsetY = 0;
669
669
  ctx.shadowColor = "transparent";
670
670
 
671
- // ── Specular highlight — bright arc on the light-facing side ──
671
+ // ── Specular highlight — tinted arc on the light-facing side ──
672
672
  if (lightAngle !== undefined && size > 15 && rng) {
673
673
  const hlRadius = size * 0.35;
674
674
  const hlDist = size * 0.15;
675
675
  const hlX = Math.cos(lightAngle) * hlDist;
676
676
  const hlY = Math.sin(lightAngle) * hlDist;
677
677
  const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, hlRadius);
678
- hlGrad.addColorStop(0, "rgba(255,255,255,0.18)");
679
- hlGrad.addColorStop(0.5, "rgba(255,255,255,0.05)");
680
- hlGrad.addColorStop(1, "rgba(255,255,255,0)");
678
+ // Tint highlight warm/cool based on fill color for cohesion
679
+ // Parse fill to detect warmth — fallback to white for non-parseable
680
+ let hlBase = "255,255,255";
681
+ if (typeof fillColor === "string" && fillColor.startsWith("#") && fillColor.length >= 7) {
682
+ const r = parseInt(fillColor.slice(1, 3), 16);
683
+ const g = parseInt(fillColor.slice(3, 5), 16);
684
+ const b = parseInt(fillColor.slice(5, 7), 16);
685
+ // Blend toward white but keep a hint of the fill's warmth
686
+ hlBase = `${Math.round(r * 0.15 + 255 * 0.85)},${Math.round(g * 0.15 + 255 * 0.85)},${Math.round(b * 0.15 + 255 * 0.85)}`;
687
+ }
688
+ hlGrad.addColorStop(0, `rgba(${hlBase},0.18)`);
689
+ hlGrad.addColorStop(0.5, `rgba(${hlBase},0.05)`);
690
+ hlGrad.addColorStop(1, `rgba(${hlBase},0)`);
681
691
  const savedOp = ctx.globalCompositeOperation;
682
692
  ctx.globalCompositeOperation = "soft-light";
683
693
  ctx.fillStyle = hlGrad;