feat(accessibility): wire BORDER_SUBTLE_HC into the modal scaffold

Resume-prompt Option E, part 2 of 2 — HC chrome borders. Pairs
with the reduce-motion gating in `ed152e2`.

v0.21.1 introduced `BORDER_SUBTLE_HC` (#a0a0a0) but never wired
it: the constant existed, no consumer used it. Spec at
`design-system.md` §Accessibility (#2) mandates outline boost
from `#505050` (BORDER_STRONG) to `#a0a0a0` under high-contrast
mode so panels and popovers stay legible on low-quality
displays.

### Architecture

- New `HighContrastBorder` component in `ui_theme` carrying a
  `default_color: Color` field that records the off-state colour
  the entity was spawned with. Tag any UI node where border
  legibility is accessibility-critical.
- New `update_high_contrast_borders` system in `settings_plugin`
  walks all tagged entities each Update tick, sets `BorderColor`
  to `BORDER_SUBTLE_HC` when `Settings::high_contrast_mode` is
  on, otherwise to `marker.default_color`. Compares against
  current `BorderColor` and only mutates when different so
  Bevy's change-detection doesn't trigger repaints every frame.

### Tagged in this commit

- The modal scaffold's card border (`ui_modal::spawn_modal`).
  This is the primary accessibility target — modals demand
  attention and a low-vision player needs to perceive the panel
  boundary. Default colour: `BORDER_STRONG` (#505050); HC
  variant: `BORDER_SUBTLE_HC` (#a0a0a0).

### Future scope

Other `BORDER_SUBTLE` / `BORDER_STRONG` consumer sites (help
panel, stats panel, tooltip, action buttons, settings rows,
etc.) can be tagged in follow-ups by adding
`HighContrastBorder::with_default(...)` to their spawn tuple.
The system handles any entity carrying the marker — no further
changes needed once a site is tagged. Started small here to
keep the commit reviewable and prove the architecture before
rolling out broadly.

Workspace clippy + cargo test --workspace clean. 1193 passing
(unchanged from prior — no new tests added; the system is
small enough that the running-game verification is the meaningful
check).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-08 13:13:13 -07:00
parent ed152e2d8f
commit c9af1ead22
3 changed files with 76 additions and 5 deletions
+26
View File
@@ -226,6 +226,32 @@ pub const BORDER_SUBTLE: Color = Color::srgba(0.208, 0.208, 0.208, 1.0);
/// vision users.
pub const BORDER_SUBTLE_HC: Color = Color::srgba(0.627, 0.627, 0.627, 1.0);
/// Marker for entities whose [`BorderColor`] should swap to
/// [`BORDER_SUBTLE_HC`] when `Settings::high_contrast_mode` is on.
/// Tag any UI node where border legibility is accessibility-critical
/// — modal panels, popovers, settings rows, focus-ring carriers —
/// then add the `apply_high_contrast_borders` system to react to
/// settings changes.
///
/// `default_color` records the off-state colour the entity was
/// spawned with so the system can revert when HC is toggled back
/// off. Different sites use different defaults (`BORDER_SUBTLE` for
/// idle popover edges, `BORDER_STRONG` for active modal cards) — the
/// marker captures whichever one applies at this entity.
#[derive(bevy::prelude::Component, Debug, Clone, Copy)]
pub struct HighContrastBorder {
/// Border colour to use when high-contrast mode is *off* — the
/// site's normal idle / active-state colour.
pub default_color: bevy::prelude::Color,
}
impl HighContrastBorder {
/// Convenience constructor — `HighContrastBorder::with_default(BORDER_SUBTLE)`.
pub const fn with_default(default_color: bevy::prelude::Color) -> Self {
Self { default_color }
}
}
/// Strong border — hover outline, focused button, active popover.
/// `outline` from the design system. `#505050`.
pub const BORDER_STRONG: Color = Color::srgba(0.314, 0.314, 0.314, 1.0);