66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
/**
|
|
* 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<T>(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<number, string> {
|
|
const map = new Map<number, string>();
|
|
orderedTeamIds.forEach((id, i) => {
|
|
map.set(id, colors[i % colors.length]!);
|
|
});
|
|
return map;
|
|
}
|
|
|
|
return { assign, colors, darken };
|
|
}
|