1/** 2 * @factory/ui — deterministic name → hue mapping (PURE). 3 * 4 * `hashNameToHue` maps any name to a stable hue in [0, 359] so avatars get a 5 * consistent color across sessions (FNV-1a, case-insensitive, trimmed). 6 * The AVATAR itself is rendered as `hsl(hue 60% 80%)`-style CSS with the hue 7 * inlined — this is the one sanctioned exception to the "no hard-coded colors" 8 * rule, because avatar colors are user-derived, not theme tokens. 9 */ 10 11/** FNV-1a 32-bit hash of a string (case-insensitive, whitespace-trimmed). */ 12export function hashString(s: string): number { 13 let h = 0x811c9dc5; 14 const text = s.trim().toLowerCase(); 15 for (let i = 0; i < text.length; i += 1) { 16 h ^= text.charCodeAt(i); 17 h = Math.imul(h, 0x01000193); 18 } 19 return h >>> 0; 20} 21 22/** Deterministic hue 0–359 for a name (stable across sessions and themes). */ 23export function hashNameToHue(name: string): number { 24 if (name.trim().length === 0) return 0; 25 return hashString(name) % 360; 26} 27 28/** CSS color string for an avatar from a name: light fill + darker text. */ 29export function avatarHsl(name: string, lightness = 82): string { 30 const hue = hashNameToHue(name); 31 return `hsl(${hue} 55% ${lightness}%)`; 32} 33