From 1ef0cf28a1fa7350f81b5b5b0e763ca8bdb9ee28 Mon Sep 17 00:00:00 2001 From: rubenperezrial <49276264+rubenperezrial@users.noreply.github.com> Date: Sun, 8 Feb 2026 04:56:06 +0100 Subject: [PATCH] feat: improve team colors with LCH color space (#3146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Refactor `generateTeamColors()` to use LCH (Lightness-Chroma-Hue) color space instead of HSL for perceptually uniform team color variations. ## Changes - **`Colors.ts`**: Rewrite `generateTeamColors()` to use LCH color space - Golden angle hue distribution clamped to ±12° to preserve team identity - Chroma oscillates ±10% around the base to add variety without washing out - Lightness alternates ±18 around the base to keep teammates recognizable ## Why LCH? LCH is a perceptually uniform color space, meaning equal numeric differences correspond to equal perceived differences. This produces team color variations that look more consistent and distinguishable compared to HSL-based generation. ## Notes - The "skip ally attack confirmation" feature that was previously in this PR has been split into a separate PR as requested by @evanpelle. --- src/core/configuration/Colors.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/core/configuration/Colors.ts b/src/core/configuration/Colors.ts index f90b034fb..37caba0f3 100644 --- a/src/core/configuration/Colors.ts +++ b/src/core/configuration/Colors.ts @@ -24,20 +24,27 @@ export const greenTeamColors: Colord[] = generateTeamColors(green); export const botTeamColors: Colord[] = [botColor]; function generateTeamColors(baseColor: Colord): Colord[] { - const hsl = baseColor.toHsl(); + const lch = baseColor.toLch(); const colorCount = 64; + const goldenAngle = 137.508; return Array.from({ length: colorCount }, (_, index) => { - const progression = index / (colorCount - 1); + if (index === 0) return baseColor; - const saturation = hsl.s * (1.0 - 0.3 * progression); - const lightness = Math.min(100, hsl.l + progression * 30); + // Spread hues evenly across ±12° band using golden angle within that range + const hueShift = ((index * goldenAngle) % 24) - 12; + const h = (lch.h + hueShift + 360) % 360; - return colord({ - h: hsl.h, - s: saturation, - l: lightness, - }); + // Chroma oscillates ±10% around the base to add variety without washing out + const chromaFactor = 1.0 + 0.1 * Math.sin(index * 0.7); + const c = Math.max(10, Math.min(130, lch.c * chromaFactor)); + + // Lightness alternates above/below the base using golden angle spacing + // Tighter range (±18) keeps teammates recognizable as the same team + const lightOffset = 18 * Math.sin(index * goldenAngle * (Math.PI / 180)); + const l = Math.max(25, Math.min(80, lch.l + lightOffset)); + + return colord({ l, c, h }); }); }