// ctp-chat.jsx — shared Call-to-Play helpers (avatar colors, clock formatting) // plus the per-call chat panel. Loaded after components.jsx, before calltoplay.jsx. const { useState: useChatState, useEffect: useChatEffect, useRef: useChatRef } = React; const CTP_AVATAR_COLORS = ['#60a5fa', '#34d399', '#c084fc', '#fbbf24', '#f472b6', '#38bdf8', '#a3e635', '#fb7185']; const ctpHashName = (s) => [...String(s)].reduce((a, c) => a + c.charCodeAt(0), 0); const ctpAvatarColor = (name) => CTP_AVATAR_COLORS[ctpHashName(name) % CTP_AVATAR_COLORS.length]; // 24h clock, LAN-party style: "20:00" const ctpFmtClock = (ts) => { const d = new Date(ts); return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }; // ──────────────────────────────────────────────────────────────────── // Per-call chat — collapsed to a one-line toggle with an unread badge // and a preview of the last message; expands to a compact IRC-style // message list + input. All state is local to the card. // ──────────────────────────────────────────────────────────────────── function CtpChat({ nomination: n, username, onSend, disabled }) { const IconX = window.Icon; const msgs = n.messages || []; const [open, setOpen] = useChatState(false); const [seen, setSeen] = useChatState(msgs.length); const [draft, setDraft] = useChatState(''); const listRef = useChatRef(null); useChatEffect(() => { if (open) setSeen(msgs.length); }, [open, msgs.length]); useChatEffect(() => { if (open && listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight; }, [open, msgs.length]); const unread = Math.max(0, msgs.length - seen); const last = msgs[msgs.length - 1]; const submit = () => { const t = draft.trim(); if (!t) return; onSend(t); setDraft(''); }; return (