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