diff --git a/lib/svg/generator.test.ts b/lib/svg/generator.test.ts
index eb43cb4a..6c3d582f 100644
--- a/lib/svg/generator.test.ts
+++ b/lib/svg/generator.test.ts
@@ -8,6 +8,7 @@ import {
escapeXML,
getSizeScale,
truncateUsername,
+ deterministicRandom,
} from './generator';
import type { BadgeParams, ContributionCalendar, StreakStats, MonthlyStats } from '../../types';
import { hexColor } from './sanitizer';
@@ -1444,3 +1445,24 @@ describe('Radar Scan Line Animation Alignment', () => {
expect(geometryLong).toEqual(geometryBaseline);
});
});
+describe('deterministicRandom', () => {
+ it('returns the same value for the same seed (determinism)', () => {
+ const seed = 'test-seed-42';
+ expect(deterministicRandom(seed)).toBe(deterministicRandom(seed));
+ });
+
+ it('result is always in the range [0, 1)', () => {
+ const seeds = ['hello', 'world', '', 'abc:def:0:offsetX', '12345'];
+ for (const seed of seeds) {
+ const result = deterministicRandom(seed);
+ expect(result).toBeGreaterThanOrEqual(0);
+ expect(result).toBeLessThan(1);
+ }
+ });
+
+ it('returns different values for different seeds', () => {
+ const a = deterministicRandom('seed-alpha');
+ const b = deterministicRandom('seed-beta');
+ expect(a).not.toBe(b);
+ });
+});
diff --git a/lib/svg/generator.ts b/lib/svg/generator.ts
index 7a8343b1..72e979c4 100644
--- a/lib/svg/generator.ts
+++ b/lib/svg/generator.ts
@@ -26,7 +26,7 @@ export function truncateUsername(username: string): string {
return username.length > 12 ? `${username.slice(0, 12)}...` : username;
}
-function deterministicRandom(seed: string): number {
+export function deterministicRandom(seed: string): number {
let hash = 2166136261;
for (let i = 0; i < seed.length; i++) {
hash ^= seed.charCodeAt(i);
@@ -930,13 +930,13 @@ export function generateWrappedSVG(
.title-user { font-family: ${selectedFont || '"Syncopate", sans-serif'}; font-weight: 700; font-size: 13px; letter-spacing: 2.5px; opacity: 0.85; }
.title-wrapped { font-family: ${selectedFont || '"Syncopate", sans-serif'}; font-weight: 700; font-size: 13px; letter-spacing: 2.5px; }
-
+
.total-commits { font-family: ${statsFont}; font-size: 46px; font-weight: 700; }
.total-label { font-family: "Roboto", sans-serif; font-size: 9.5px; font-weight: 700; letter-spacing: 1.5px; opacity: 0.5; }
-
+
.grid-label { font-family: "Roboto", sans-serif; font-size: 9px; font-weight: 700; letter-spacing: 1.5px; opacity: 0.5; }
.grid-val { font-family: ${statsFont}; font-size: 14.5px; font-weight: 600; }
-
+
.scan-line {
animation: scan-sweep var(--scan-speed, 8s) linear infinite;
transform-box: fill-box;
@@ -1085,11 +1085,11 @@ function generateAutoThemeMonthlySVG(stats: MonthlyStats, params: BadgeParams):
${googleFontsImport}
:root { --cp-bg: #${light.bg}; --cp-text: #${light.text}; --cp-accent: #${light.accent}; --cp-negative: #${light.negative || 'cf222e'}; }
@media (prefers-color-scheme: dark) { :root { --cp-bg: #${dark.bg}; --cp-text: #${dark.text}; --cp-accent: #${dark.accent}; --cp-negative: #${dark.negative || 'f85149'}; } }
- .cp-bg-fill { fill: var(--cp-bg); }
- .cp-text-fill { fill: var(--cp-text); color: var(--cp-text); }
+ .cp-bg-fill { fill: var(--cp-bg); }
+ .cp-text-fill { fill: var(--cp-text); color: var(--cp-text); }
.cp-accent-fill { fill: var(--cp-accent); color: var(--cp-accent); }
.cp-delta-fill { fill: ${stats.deltaAbsolute >= 0 ? 'var(--cp-accent)' : 'var(--cp-negative)'}; }
-
+
.title { font-family: ${selectedFont || '"Syncopate", sans-serif'}; fill: var(--cp-text); font-size: 14px; letter-spacing: 2px; font-weight: 400; opacity: 0.8; }
.stats { font-family: ${statsFont}; fill: var(--cp-accent); font-size: 36px; font-weight: 600; letter-spacing: 0; }
.label { font-family: "Roboto", sans-serif; fill: var(--cp-text); font-size: 10px; font-weight: 400; letter-spacing: 1px; opacity: 0.7; }
@@ -1776,7 +1776,7 @@ export function generateVersusSVG(
${renderDefs(sf, params)}
${renderStyle(selectedFont, statsFont, googleFontsImport, text, accent, sf, bg)}
-
+
${towers1}
${renderIsometricLabels(calendar1, params, text, sf)}
@@ -1790,7 +1790,7 @@ export function generateVersusSVG(
-
+
VS
@@ -1911,7 +1911,7 @@ function generateAutoThemeVersusSVG(
CommitPulse Versus Stats: ${safeUser1} vs ${safeUser2}
${safeUser1} has ${stats1.totalContributions} ${unit}. ${safeUser2} has ${stats2.totalContributions} ${unit}.
${renderDefs(sf, params)}
-
+
-
+
${towers1}
${renderIsometricLabels(calendar1, params, '', sf)}
@@ -1958,7 +1958,7 @@ function generateAutoThemeVersusSVG(
-
+
VS