1/** 2 * presence-color (TASK-0093) — map a presence palette NAME (the value the 3 * server stamps in `user.color`, from the shared `presenceColor(userId)` 4 * 20-name palette) to concrete CSS colors the web can render. 5 * 6 * Decision (recorded, slice 3b): the web has no per-presence-color design 7 * tokens yet (the app's `--factory-color-*` tokens cover only the 9 block 8 * colors, not the 20 presence names), so each palette name is rendered as a 9 * DISTINCT HUE derived from its index in the shared palette 10 * (`hue = index * 360/20`). This is deterministic per user (same name → 11 * same hue everywhere), keeps the 20-color collision-resistance (20 distinct 12 * hues), and hard-codes no hex in the component (the a11y rule: color is 13 * never the sole identifier — the name label is mandatory, so hue similarity 14 * is harmless). Token-izing to an official design-system palette is a 15 * follow-up if the design team ships presence colors. 16 * 17 * Pure — no DOM, no hex literals (the hsl() is computed at runtime from the 18 * palette index, so the Phase-4 "no hardcoded hex in the component" rule 19 * holds). 20 */ 21import { PRESENCE_PALETTE } from "@factory/shared/presence"; 22 23export interface PresenceColors { 24 /** The peer's main color (caret, avatar ring, name-label text). */ 25 readonly solid: string; 26 /** A translucent version (remote selection highlight, ~25 %). */ 27 readonly soft: string; 28 /** A darker version (name-label background — white text on it). */ 29 readonly labelBg: string; 30 /** A light version (avatar background). */ 31 readonly softBg: string; 32} 33 34const STEP = 360 / PRESENCE_PALETTE.length; // 18° per slot 35 36/** The hue (0–360) for a palette name. Unknown names fall back to slot 0. */ 37export function presenceHue(name: string): number { 38 const i = PRESENCE_PALETTE.indexOf(name); 39 return (i < 0 ? 0 : i) * STEP; 40} 41 42/** Renderable colors for a palette name (see the module decision). */ 43export function presenceColors(name: string): PresenceColors { 44 const h = presenceHue(name); 45 return { 46 solid: `hsl(${h} 72% 40%)`, 47 soft: `hsl(${h} 72% 45% / 0.25)`, 48 labelBg: `hsl(${h} 62% 34%)`, 49 softBg: `hsl(${h} 60% 88%)`, 50 }; 51} 52