Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,8 @@
"keybinds_hint": "Click a key to rebind it. You can assign a single key or Shift + key combination.",
"dark_mode_label": "Dark Mode",
"dark_mode_desc": "Toggle the site’s appearance between light and dark themes",
"colorblind_label": "Colorblind Mode",
"colorblind_desc": "Use colorblind-friendly territory and border colors",
"emojis_label": "Emojis",
"emojis_desc": "Toggle whether emojis are shown in game",
"alert_frame_label": "Alert Frame",
Expand Down Expand Up @@ -930,6 +932,7 @@
"colored": "Colored",
"black": "Black",
"section_structure_icons": "Structure Icons",
"section_accessibility": "Accessibility",
"classic_icons_label": "Classic icons",
"classic_icons_desc": "Lighter outline with near-black interior",
"reset_label": "Reset to defaults",
Expand Down
28 changes: 28 additions & 0 deletions src/client/UserSettingModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,25 @@ export class UserSettingModal extends BaseModal {
console.log("🌙 Dark Mode:", this.userSettings.darkMode() ? "ON" : "OFF");
}

/** Whether colorblind mode is currently enabled in the graphics overrides. */
private colorblindMode(): boolean {
return (
this.userSettings.graphicsOverrides().accessibility?.colorblind ?? false
);
}

/** Flip the colorblind-mode graphics override and persist it. */
private toggleColorblindMode() {
const overrides = this.userSettings.graphicsOverrides();
this.userSettings.setGraphicsOverrides({
...overrides,
accessibility: {
...overrides.accessibility,
colorblind: !this.colorblindMode(),
},
});
}

private toggleEmojis() {
this.userSettings.toggleEmojis();

Expand Down Expand Up @@ -751,6 +770,15 @@ export class UserSettingModal extends BaseModal {
@change=${this.toggleDarkMode}
></setting-toggle>

<!-- 🎨 Colorblind Mode -->
<setting-toggle
label="${translateText("user_setting.colorblind_label")}"
description="${translateText("user_setting.colorblind_desc")}"
id="colorblind-toggle"
.checked=${this.colorblindMode()}
@change=${this.toggleColorblindMode}
></setting-toggle>

<!-- 😊 Emojis -->
<setting-toggle
label="${translateText("user_setting.emojis_label")}"
Expand Down
50 changes: 50 additions & 0 deletions src/client/hud/layers/GraphicsSettingsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,30 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
this.patchStructure({ classicIcons: !this.currentClassicIcons() });
}

/** Merge a patch into the accessibility graphics overrides and persist it. */
private patchAccessibility(
patch: Partial<GraphicsOverrides["accessibility"]>,
) {
const current = this.userSettings.graphicsOverrides();
this.userSettings.setGraphicsOverrides({
...current,
accessibility: { ...current.accessibility, ...patch },
});
this.requestUpdate();
}

/** Whether colorblind mode is currently enabled. */
private currentColorblind(): boolean {
return (
this.userSettings.graphicsOverrides().accessibility?.colorblind ?? false
);
}

/** Toggle colorblind-friendly colors. */
private onToggleColorblind() {
this.patchAccessibility({ colorblind: !this.currentColorblind() });
}

private onNameScaleChange(event: Event) {
const value = parseFloat((event.target as HTMLInputElement).value);
this.patchName({ nameScaleFactor: value });
Expand Down Expand Up @@ -179,6 +203,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
const nameCull = this.currentNameCull();
const namesColored = !this.currentDarkNames();
const classicIcons = this.currentClassicIcons();
const colorblind = this.currentColorblind();

return html`
<div
Expand Down Expand Up @@ -309,6 +334,31 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
</div>
</button>

<div
class="px-3 py-1 text-xs font-semibold text-slate-400 uppercase tracking-wider mt-2"
>
${translateText("graphics_setting.section_accessibility")}
</div>

<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
@click=${this.onToggleColorblind}
>
<div class="flex-1">
<div class="font-medium">
${translateText("user_setting.colorblind_label")}
</div>
<div class="text-sm text-slate-400">
${translateText("user_setting.colorblind_desc")}
</div>
</div>
<div class="text-sm text-slate-400">
${colorblind
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>

<div class="border-t border-slate-600 pt-3 mt-4">
<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
Expand Down
5 changes: 5 additions & 0 deletions src/client/render/gl/GraphicsOverrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export const GraphicsOverridesSchema = z
classicIcons: z.boolean(),
})
.partial(),
accessibility: z
.object({
colorblind: z.boolean(),
})
.partial(),
})
.partial();

Expand Down
33 changes: 33 additions & 0 deletions src/client/render/gl/RenderOverrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { RenderSettings } from "./RenderSettings";

const DARK_AMBIENT = 0.35;

/**
* Apply the user's graphics overrides onto a RenderSettings in place: name
* scaling, classic/dark structure and name styling, and the colorblind-safe
* affiliation/tint palette.
*/
export function applyGraphicsOverrides(
settings: RenderSettings,
overrides: GraphicsOverrides,
Expand Down Expand Up @@ -36,8 +41,36 @@ export function applyGraphicsOverrides(
settings.name.outlineG = channel;
settings.name.outlineB = channel;
}
if (overrides.accessibility?.colorblind === true) {
// Swap the red/green friend-foe encoding (the most common confusion axis)
// for a colorblind-safe blue/orange pairing (Okabe-Ito).
// Alt-view affiliation borders: self/ally in the blue family, enemy orange.
settings.affiliation.selfR = 0;
settings.affiliation.selfG = 0.447;
settings.affiliation.selfB = 0.698;
settings.affiliation.allyR = 0.337;
settings.affiliation.allyG = 0.706;
settings.affiliation.allyB = 0.914;
settings.affiliation.enemyR = 0.835;
settings.affiliation.enemyG = 0.369;
settings.affiliation.enemyB = 0;
// Normal-view relationship border tints: friendly blue, enemy orange,
// applied strongly so the cue doesn't rely on subtle hue.
settings.mapOverlay.friendlyTintR = 0;
settings.mapOverlay.friendlyTintG = 0.447;
settings.mapOverlay.friendlyTintB = 0.698;
settings.mapOverlay.embargoTintR = 0.835;
settings.mapOverlay.embargoTintG = 0.369;
settings.mapOverlay.embargoTintB = 0;
// Strong ratio so the friend/foe tint dominates the darkened territory
// border — neutral keeps its (darkened) fill hue, ally reads blue, enemy
// reads orange.
settings.mapOverlay.friendlyTintRatio = 0.85;
settings.mapOverlay.embargoTintRatio = 0.85;
}
}

/** Apply dark-mode lighting (ambient + enabled) onto settings when active. */
export function applyDarkModeOverride(
settings: RenderSettings,
isDark: boolean,
Expand Down
Loading
Loading