feat(client): graphics presets — simplify settings behind an Advanced section (#4663)

## Summary

The graphics settings modal had grown too complex for average players
(~25 sliders/toggles/color pickers in one flat list). This reworks
graphics configuration around **presets**:

- **Shared `<graphics-preset-selector>` component**
(`src/client/components/`): a self-contained dropdown of built-in +
saved presets that applies a preset wholesale, shows a disabled "Custom"
entry while the current overrides match no preset, and offers delete for
saved presets. It reads/writes `UserSettings` directly and re-renders on
the settings-changed events, so both hosts stay in sync automatically.
- **In-game graphics modal**: preset dropdown up top, everything else
collapsed under an **Advanced settings** expander. Advanced also holds
the custom-preset tools: save-as-preset, copy settings JSON to
clipboard, and paste-JSON import (Zod-validated, inline error).
- **Main-menu settings modal**: a new "Graphics Preset" row using the
same component — replacing the colorblind toggle.
- **Built-in presets**: Default, Night, and Colorblind — defined as data
in `graphics-presets.json` (schema-parsed at load).

### Design notes

- **Theme palettes are an enum, not a boolean.**
`accessibility.colorblind` is replaced by `palette: "default" |
"colorblind"` — `PALETTE_NAMES` in `GraphicsOverrides.ts` is the single
source of truth (`ThemeName` derives from it; `ThemeProvider` builds one
theme per palette), so adding a future palette is one enum entry + one
theme JSON. Legacy stored booleans are translated to the enum at read
time.
- **The colorblind look is data.** The previously hardcoded Okabe-Ito
friend-foe colors are now generic override fields
(`affiliation.self/ally/enemyColor`,
`mapOverlay.friendly/embargoTintColor` + ratios) carried by the
Colorblind preset JSON; the palette selects only the theme. Selecting
Colorblind anywhere (menu or in-game) applies the full look. Side
benefit: any preset can customize friend/foe border colors.
- **Existing players are protected**: a one-time migration snapshots
pre-existing custom overrides into a "My settings" preset on first init,
so switching presets can never lose a hand-tuned config. Flag-only
colorblind users are upgraded to the full Colorblind preset so they keep
the blue/orange borders. Writing the presets key (even as `{}`) stamps
the migration done.
- Applying a preset reuses the existing `setGraphicsOverrides` →
change-event → live-regenerate path, so presets and imports take effect
immediately (including terrain-texture rebuilds for color changes).

## Test plan

- [x] Full suite: 176 files / 2100 tests pass; `tsc`, ESLint, Prettier
clean
- [x] `GraphicsPresets.test.ts` locks preset JSON to the renderer values
the old hardcoded colorblind block produced, schema-validates every
built-in preset, and covers legacy boolean → palette-enum translation
- [x] Driven headless in a real singleplayer game (WebGL): dropdown
present in both the main-menu settings and the in-game modal; selection
in one reflects in the other; presets apply + persist; "Custom" appears
after a manual slider tweak; save-as-preset selects the new entry;
delete via ✕ + confirm; copy JSON → clipboard; invalid-JSON inline
error; valid import applies live (ocean recolor visible in render)
- [x] Migration: seeded legacy overrides survive init untouched and
appear as an active "My settings" preset; legacy colorblind boolean
upgrades to the full preset; fresh profiles get no phantom preset;
migration is idempotent

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Evan
2026-07-21 16:11:43 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 6734f76488
commit 1360b27bbd
15 changed files with 1660 additions and 921 deletions
+24 -3
View File
@@ -596,6 +596,8 @@
"title": "Game is Starting..."
},
"graphics_setting": {
"advanced_desc": "Fine-tune individual graphics options",
"advanced_label": "Advanced settings",
"black": "Black",
"classic_icons_desc": "Lighter outline with near-black interior",
"classic_icons_label": "Classic icons",
@@ -606,6 +608,9 @@
"colored_names_label": "Name color",
"coordinate_grid_opacity_desc": "How opaque the coordinate grid is (lower lets more things show through)",
"coordinate_grid_opacity_label": "Coordinate grid opacity",
"copy_json_copied": "Copied!",
"copy_json_desc": "Copy the current settings as JSON to share with friends",
"copy_json_label": "Copy settings JSON",
"fallout_desc": "Show the green nuclear fallout glow on irradiated territory. Disable to improve performance",
"fallout_label": "Fallout effects",
"highland_color_desc": "Base color for higher-elevation terrain.",
@@ -624,6 +629,10 @@
"hover_glow_width_label": "Hover glow size",
"icon_size_desc": "How large structure icons are drawn on the map",
"icon_size_label": "Structure icon size",
"import_json_apply": "Apply",
"import_json_desc": "Paste settings JSON from a friend and apply it",
"import_json_invalid": "That doesn't look like valid settings JSON",
"import_json_label": "Import settings JSON",
"lighting_ambient_desc": "Darkens the map and adds a glow around structures (0 = off)",
"lighting_ambient_label": "Ambient light",
"lighting_unit_glow_desc": "How far the glow spreads around units and structures",
@@ -641,6 +650,16 @@
"ocean_color_label": "Ocean color",
"plains_color_desc": "Base color for lowland terrain.",
"plains_color_label": "Plains color",
"preset_colorblind": "Colorblind",
"preset_colorblind_desc": "Colorblind-friendly territory and border colors",
"preset_custom": "Custom",
"preset_default": "Default",
"preset_default_desc": "The standard OpenFront look",
"preset_delete_confirm": "Delete preset \"{name}\"?",
"preset_migrated_name": "My settings",
"preset_name_placeholder": "Preset name",
"preset_night": "Night",
"preset_night_desc": "Darkened map with glowing structures and units",
"rail_distance_desc": "How far zoomed out train tracks remain visible",
"rail_distance_label": "Train track draw distance",
"rail_thickness_desc": "How wide train tracks are drawn",
@@ -649,11 +668,13 @@
"reset_label": "Reset to defaults",
"sand_color_desc": "Base color for shores.",
"sand_color_label": "Shore color",
"section_accessibility": "Accessibility",
"save_preset_label": "Save",
"section_custom_presets": "Custom presets",
"section_effects": "Effects",
"section_lighting": "Lighting",
"section_map": "Map",
"section_name_labels": "Name Labels",
"section_presets": "Presets",
"section_structure_icons": "Structure Icons",
"section_terrain": "Terrain",
"structure_dots_desc": "Collapse structure icons into small dots when zoomed out",
@@ -1526,8 +1547,6 @@
"camera_movement": "Camera Movement",
"center_camera": "Center Camera",
"center_camera_desc": "Center camera on player",
"colorblind_desc": "Use colorblind-friendly territory and border colors",
"colorblind_label": "Colorblind Mode",
"coordinate_grid_desc": "Toggle the alphanumeric grid overlay",
"coordinate_grid_label": "Coordinate Grid",
"cursor_cost_label_desc": "Show a cost pill under the build cursor icon",
@@ -1549,6 +1568,8 @@
"game_speed_up_desc": "Cycle to next game speed (0.5, 1, 2, max). Single player only.",
"go_to_player_desc": "Toggle zooming in on the player in the beginning of a game.",
"go_to_player_label": "Go to player on start",
"graphics_preset_desc": "Choose the preset look for the map. Fine-tune or make your own in the in-game graphics settings.",
"graphics_preset_label": "Graphics Preset",
"graphics_refresh_modifier": "Graphics refresh modifier",
"graphics_refresh_modifier_desc": "Hold this key and R to refresh graphics",
"graphics_settings_desc": "Adjust how the map looks",