Feature/colorblind mode (#4150)

**Add approved & assigned issue number here:**

Resolves #2549

## Description:

Adds colorblind mode. Similar to dark mode, it exists as a toggle in
settings. When enabled, it swaps the game's theme (which is refactored
to extend from a theme base class) to use more colorblind-friendly
colors and brightness variations. Borders are darkened, and terrarin is
separated by lightness. Friendly/Foe colors and switched to blue/orange
instead of red/green.

The theme refactor supports adding new themes without having to
reimplement the color distribution system. New themes can extend the
BaseTheme and supply the data, such as palettes, team-color variations,
and terrain.

New setting:
<img width="880" height="273" alt="Screenshot 2026-06-04 at 11 30 27 AM"
src="https://github.com/user-attachments/assets/d5d573d5-cc64-4ac1-95c2-00627faf17cc"
/>

New color palette:
<img width="1119" height="757" alt="Screenshot 2026-06-04 at 11 30
59 AM"
src="https://github.com/user-attachments/assets/2bb15bc9-992b-41ae-ab0e-b01fe0c3c6bb"
/>

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

jetaviz
This commit is contained in:
noahschmal
2026-06-11 10:53:03 -07:00
committed by GitHub
parent 7137347b7d
commit 21776e81af
16 changed files with 723 additions and 304 deletions
+33 -15
View File
@@ -105,19 +105,20 @@ export class PlayerView {
/** Static header data — set once at construction, never mutated. */
public static: PlayerStatic;
private _territoryColor: Colord;
private _borderColor: Colord;
private _railColor: Colord;
// Assigned via computeColors() in the constructor; re-assignable on theme change.
private _territoryColor!: Colord;
private _borderColor!: Colord;
private _railColor!: Colord;
// Update here to include structure light and dark colors
private _structureColors: { light: Colord; dark: Colord };
private _structureColors!: { light: Colord; dark: Colord };
// Pre-computed border color variants
private _borderColorNeutral: Colord;
private _borderColorFriendly: Colord;
private _borderColorEmbargo: Colord;
private _borderColorDefendedNeutral: { light: Colord; dark: Colord };
private _borderColorDefendedFriendly: { light: Colord; dark: Colord };
private _borderColorDefendedEmbargo: { light: Colord; dark: Colord };
private _borderColorNeutral!: Colord;
private _borderColorFriendly!: Colord;
private _borderColorEmbargo!: Colord;
private _borderColorDefendedNeutral!: { light: Colord; dark: Colord };
private _borderColorDefendedFriendly!: { light: Colord; dark: Colord };
private _borderColorDefendedEmbargo!: { light: Colord; dark: Colord };
constructor(
private game: GameView,
@@ -135,6 +136,23 @@ export class PlayerView {
this.anonymousName = createRandomName(data.name!, data.playerType!);
}
this.computeColors();
const pattern = userSettings.territoryPatterns()
? this.cosmetics.pattern
: undefined;
this.decoder =
pattern === undefined
? undefined
: new PatternDecoder(pattern, base64url.decode);
}
/**
* Compute every theme-derived color (fill, border, structure, and the
* neutral/friendly/embargo border variants) from the active theme. Re-callable
* so a mid-game theme change — e.g. toggling colorblind mode — can refresh them.
*/
private computeColors(): void {
const theme = themeProvider.current();
const defaultTerritoryColor = theme.territoryColor(this);
@@ -164,7 +182,7 @@ export class PlayerView {
this._structureColors = theme.structureColors(this._territoryColor);
const maybeFocusedBorderColor =
this.game.myClientID() === data.clientID
this.game.myClientID() === this.static.clientID
? theme.focusedBorderColor()
: defaultBorderColor;
@@ -230,11 +248,11 @@ export class PlayerView {
this._borderColorDefendedEmbargo = theme.defendedBorderColors(
this._borderColorEmbargo,
);
}
this.decoder =
pattern === undefined
? undefined
: new PatternDecoder(pattern, base64url.decode);
/** Recompute colors after the active theme changes (e.g. colorblind toggle). */
refreshColors(): void {
this.computeColors();
}
/**