feat: user-choosable avatar colors with settings picker

Add avatarColor as a stored, user-selectable field (mint, sky, lavender,
coral, rose, teal, amber). Randomly assigned on registration, changeable
in profile settings. Existing users keep hash-based fallback until they
choose a color. Includes DB migration, API validation, gradient map,
live preview in settings, and banner fallback integration.
This commit is contained in:
Jannis Braun
2026-03-10 20:26:28 +01:00
parent d7449bdf42
commit d2697d87fa
13 changed files with 95 additions and 10 deletions
+17 -2
View File
@@ -3,6 +3,8 @@
* Deterministic color assignment based on entity ID (snowflake) or name.
*/
import type { AvatarColor } from '@backspace/shared';
export interface GradientEntry {
gradient: string;
glow: string;
@@ -19,6 +21,16 @@ const AVATAR_GRADIENTS: GradientEntry[] = [
{ gradient: 'linear-gradient(135deg, #f59e0b, #eab308)', glow: '#f59e0b' }, // amber
];
export const AVATAR_GRADIENT_MAP: Record<AvatarColor, GradientEntry> = {
mint: AVATAR_GRADIENTS[0]!,
sky: AVATAR_GRADIENTS[1]!,
lavender: AVATAR_GRADIENTS[2]!,
coral: AVATAR_GRADIENTS[3]!,
rose: AVATAR_GRADIENTS[4]!,
teal: AVATAR_GRADIENTS[5]!,
amber: AVATAR_GRADIENTS[6]!,
};
// ── Space icon gradients (space fallbacks) ──
const SPACE_GRADIENTS: GradientEntry[] = [
{ gradient: 'linear-gradient(135deg, #ef4444, #f97316)', glow: '#f97316' }, // red-orange
@@ -45,8 +57,11 @@ function hashString(str: string): number {
return hash;
}
/** Deterministic gradient for a user avatar. Prefers ID for stability; falls back to name. */
export function getAvatarGradient(id?: string | null, name?: string): GradientEntry {
/** Deterministic gradient for a user avatar. Uses stored avatarColor if available; falls back to hash. */
export function getAvatarGradient(id?: string | null, name?: string, avatarColor?: string | null): GradientEntry {
if (avatarColor && avatarColor in AVATAR_GRADIENT_MAP) {
return AVATAR_GRADIENT_MAP[avatarColor as AvatarColor];
}
const key = id || name || 'unknown';
return AVATAR_GRADIENTS[hashString(key) % AVATAR_GRADIENTS.length]!;
}