/** * Client-only team color assignment for the live map. * * A fixed 20-color high-contrast palette is shuffled once per page-load and * assigned to teams by their stable order (the order returned by the map * endpoint). Colors are team-shared but NOT persisted, so a refresh may * reassign them. Every member of a team resolves to the same color. */ const PALETTE = [ '#e6194b', '#3cb44b', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ff4d00' ] as const; function shuffled(input: readonly T[]): T[] { const arr = [...input]; for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j]!, arr[i]!]; } return arr; } /** Darken a hex color by a factor (0..1) for trail lines. */ export function darken(hex: string, factor = 0.75): string { const n = parseInt(hex.slice(1), 16); const r = Math.round(((n >> 16) & 255) * factor); const g = Math.round(((n >> 8) & 255) * factor); const b = Math.round((n & 255) * factor); return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`; } export function useTeamColors() { // Shuffled once per composable instance (i.e. per page-load). const colors = shuffled(PALETTE); /** Build a teamId -> color map from an ordered list of team ids. */ function assign(orderedTeamIds: number[]): Map { const map = new Map(); orderedTeamIds.forEach((id, i) => { map.set(id, colors[i % colors.length]!); }); return map; } return { assign, colors, darken }; }