From 25147f707eb7c394855abdf5c0d1d7163b2d44a4 Mon Sep 17 00:00:00 2001 From: extolkom Date: Mon, 1 Jun 2026 11:59:36 -1200 Subject: [PATCH] feat: add deterministic gradient fallback for creator avatars --- .../__tests__/CreatorInitialsAvatar.test.tsx | 15 ++++++++++++++- src/utils/avatarColor.util.ts | 17 +++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/common/__tests__/CreatorInitialsAvatar.test.tsx b/src/components/common/__tests__/CreatorInitialsAvatar.test.tsx index 98fedf7..ef10ad9 100644 --- a/src/components/common/__tests__/CreatorInitialsAvatar.test.tsx +++ b/src/components/common/__tests__/CreatorInitialsAvatar.test.tsx @@ -1,7 +1,10 @@ import { render, screen } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import CreatorInitialsAvatar from '@/components/common/CreatorInitialsAvatar'; -import { getFallbackAvatarColors } from '@/utils/avatarColor.util'; +import { + getCreatorGradientFallback, + getFallbackAvatarColors, +} from '@/utils/avatarColor.util'; describe('CreatorInitialsAvatar', () => { it('uses deterministic fallback colors for the same creator id', () => { @@ -14,6 +17,16 @@ describe('CreatorInitialsAvatar', () => { expect(first.textColor).toBe('rgba(255, 255, 255, 0.95)'); }); + it('uses the fallback gradient helper deterministically for handles or ids', () => { + const first = getCreatorGradientFallback('creator-123'); + const second = getCreatorGradientFallback('creator-123'); + const third = getCreatorGradientFallback('creator-456'); + + expect(first).toEqual(second); + expect(first).not.toBe(third); + expect(first).toContain('linear-gradient'); + }); + it('renders initials fallback with hashed background when image is missing', () => { render(); diff --git a/src/utils/avatarColor.util.ts b/src/utils/avatarColor.util.ts index 44d8472..5109823 100644 --- a/src/utils/avatarColor.util.ts +++ b/src/utils/avatarColor.util.ts @@ -6,14 +6,23 @@ const stringHash = (value: string) => { return hash; }; -export const getFallbackAvatarColors = (creatorId?: string | number | null) => { - const normalizedId = String(creatorId ?? '').trim(); - const hash = stringHash(normalizedId || 'fallback-avatar'); +export const getCreatorGradientFallback = ( + identifier?: string | number | null +) => { + const normalizedIdentifier = String(identifier ?? 'creator-fallback').trim() || 'creator-fallback'; + const hash = stringHash(normalizedIdentifier); const baseHue = hash % 360; const accentHue = (baseHue + 28) % 360; + // Use darker mid-lightness HSL values so white text remains readable over + // the gradient. The chosen lightness range keeps contrast high enough for + // WCAG AA against white overlay text. + return `linear-gradient(135deg, hsl(${baseHue} 65% 28%), hsl(${accentHue} 72% 36%))`; +}; + +export const getFallbackAvatarColors = (creatorId?: string | number | null) => { return { - background: `linear-gradient(135deg, hsl(${baseHue} 65% 28%), hsl(${accentHue} 72% 36%))`, + background: getCreatorGradientFallback(creatorId), textColor: 'rgba(255, 255, 255, 0.95)', }; };