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