From c50eaf81f7096f81c9bd7d0e39763cb4b64f7fae Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 8 May 2026 18:19:00 -0700 Subject: [PATCH] feat(replay): add HC bump for WIN MOVE scrub-bar marker; extend HighContrastBackground MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HighContrastBackground gains an optional hc_color field so sites can specify a domain-specific HC variant rather than always bumping to BORDER_SUBTLE_HC (gray). with_default() fills hc_color = BORDER_SUBTLE_HC preserving all existing behaviour; new with_hc(default, hc) lets callers specify both ends. update_high_contrast_backgrounds reads marker.hc_color instead of the hardcoded constant. STATE_SUCCESS_HC (#c8e862, L≈0.73) added to ui_theme — a brighter lime that maintains the success hue while standing out from bumped notch ticks (BORDER_SUBTLE_HC gray, L≈0.60) under HC mode. WIN MOVE marker now carries HighContrastBackground::with_hc(STATE_SUCCESS, STATE_SUCCESS_HC): lime stays lime under HC instead of turning gray. Unit test pins both the default and hc color fields on the spawned marker. 1276 tests pass / 0 failing. Co-Authored-By: Claude Opus 4.7 --- solitaire_engine/src/replay_overlay.rs | 47 +++++++++++++++++++++++-- solitaire_engine/src/settings_plugin.rs | 2 +- solitaire_engine/src/ui_theme.rs | 42 ++++++++++++++++++---- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/solitaire_engine/src/replay_overlay.rs b/solitaire_engine/src/replay_overlay.rs index 6f791ac..8e329a9 100644 --- a/solitaire_engine/src/replay_overlay.rs +++ b/solitaire_engine/src/replay_overlay.rs @@ -38,8 +38,8 @@ use solitaire_data::ReplayMove; use crate::ui_modal::{spawn_modal_button, ButtonVariant}; use crate::ui_theme::{ ACCENT_PRIMARY, BG_ELEVATED_HI, BORDER_SUBTLE, HighContrastBackground, HighContrastBorder, - STATE_SUCCESS, TEXT_PRIMARY, TEXT_PRIMARY_HC, TEXT_SECONDARY, TYPE_BODY, TYPE_CAPTION, - TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_4, Z_DROP_OVERLAY, + STATE_SUCCESS, STATE_SUCCESS_HC, TEXT_PRIMARY, TEXT_PRIMARY_HC, TEXT_SECONDARY, TYPE_BODY, + TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_4, Z_DROP_OVERLAY, }; // --------------------------------------------------------------------------- @@ -779,6 +779,11 @@ fn spawn_overlay( ..default() }, BackgroundColor(STATE_SUCCESS), + // HC bump: lime → brighter lime so the win + // marker reads clearly above the bumped + // notch ticks (BORDER_SUBTLE_HC gray) under + // high-contrast mode. + HighContrastBackground::with_hc(STATE_SUCCESS, STATE_SUCCESS_HC), )); } // Fixed quarter-mark notches: five 1px vertical @@ -2349,6 +2354,44 @@ mod tests { ); } + /// The WIN MOVE marker carries `HighContrastBackground::with_hc( + /// STATE_SUCCESS, STATE_SUCCESS_HC)` so the lime bumps to brighter + /// lime under HC mode rather than to a neutral gray. Pin the + /// presence of the marker so a future refactor can't accidentally + /// drop it and silently regress HC legibility. + #[test] + fn win_move_marker_carries_hc_background_marker() { + let mut app = headless_app(); + set_state( + &mut app, + ReplayPlaybackState::Playing { + replay: synthetic_replay(8).with_win_move_index(Some(7)), + cursor: 0, + secs_to_next: 0.5, + paused: false, + }, + ); + app.update(); + + let mut q = app + .world_mut() + .query_filtered::<&HighContrastBackground, With>(); + let marker = q + .iter(app.world()) + .next() + .expect("WIN MOVE marker must carry HighContrastBackground"); + assert_eq!( + marker.default_color, + STATE_SUCCESS, + "default colour must be STATE_SUCCESS" + ); + assert_eq!( + marker.hc_color, + STATE_SUCCESS_HC, + "HC colour must be STATE_SUCCESS_HC (brighter lime, not gray)" + ); + } + // ----------------------------------------------------------------------- // scrub_notch_positions + ReplayOverlayScrubNotch spawn behaviour // ----------------------------------------------------------------------- diff --git a/solitaire_engine/src/settings_plugin.rs b/solitaire_engine/src/settings_plugin.rs index b84b267..97a22b1 100644 --- a/solitaire_engine/src/settings_plugin.rs +++ b/solitaire_engine/src/settings_plugin.rs @@ -701,7 +701,7 @@ pub(crate) fn update_high_contrast_backgrounds( let high_contrast = settings.0.high_contrast_mode; for (marker, mut bg) in backgrounds.iter_mut() { let target = if high_contrast { - BORDER_SUBTLE_HC + marker.hc_color } else { marker.default_color }; diff --git a/solitaire_engine/src/ui_theme.rs b/solitaire_engine/src/ui_theme.rs index b9a8351..05e404f 100644 --- a/solitaire_engine/src/ui_theme.rs +++ b/solitaire_engine/src/ui_theme.rs @@ -93,6 +93,13 @@ pub const ACCENT_SECONDARY: Color = Color::srgb(0.882, 0.639, 0.933); /// from base16-eighties. `#acc267`. pub const STATE_SUCCESS: Color = Color::srgb(0.675, 0.761, 0.404); +/// High-contrast variant of [`STATE_SUCCESS`] — `#c8e862`. Brighter +/// lime that maintains the success hue while lifting luminance from +/// ~0.51 → ~0.73 so the WIN MOVE scrub-bar marker stands out from +/// the bumped notch ticks (`BORDER_SUBTLE_HC` `#a0a0a0`, L≈0.60) in +/// high-contrast mode. +pub const STATE_SUCCESS_HC: Color = Color::srgb(0.784, 0.910, 0.384); + /// Warning — penalty signal, daily-seed expiry countdown, sync-pending /// status. Gold from base16-eighties. **Both** Undo and Recycle /// counters use this when non-zero. `#ddb26f`. @@ -260,24 +267,45 @@ impl HighContrastBorder { /// often render as tiny full-bleed `Node`s, not as borders, so the /// border-marker pattern doesn't apply. /// -/// `default_color` records the off-state colour the entity was -/// spawned with so the system can revert when HC is toggled back -/// off. The accompanying paint system is -/// [`update_high_contrast_backgrounds`](crate::settings_plugin::update_high_contrast_backgrounds). +/// `default_color` records the off-state colour; `hc_color` the on- +/// state colour. [`with_default`] fills `hc_color` with +/// [`BORDER_SUBTLE_HC`] so the 90 % of sites that just need the +/// standard subtle-border bump can continue using a one-argument +/// constructor. [`with_hc`] overrides the HC colour for the rare +/// site (currently only the WIN MOVE scrub-bar marker) that needs a +/// domain-specific HC variant (`STATE_SUCCESS_HC` instead of a gray). /// +/// [`with_default`]: HighContrastBackground::with_default +/// [`with_hc`]: HighContrastBackground::with_hc /// [`BackgroundColor`]: bevy::prelude::BackgroundColor #[derive(bevy::prelude::Component, Debug, Clone, Copy)] pub struct HighContrastBackground { /// Background colour to use when high-contrast mode is *off* — /// the site's normal idle / active-state colour. pub default_color: bevy::prelude::Color, + /// Background colour to use when high-contrast mode is *on*. + /// Defaults to [`BORDER_SUBTLE_HC`] via [`with_default`]. + /// + /// [`with_default`]: HighContrastBackground::with_default + pub hc_color: bevy::prelude::Color, } impl HighContrastBackground { - /// Convenience constructor — - /// `HighContrastBackground::with_default(BORDER_SUBTLE)`. + /// Convenience constructor — HC colour defaults to + /// [`BORDER_SUBTLE_HC`]. pub const fn with_default(default_color: bevy::prelude::Color) -> Self { - Self { default_color } + Self { default_color, hc_color: BORDER_SUBTLE_HC } + } + + /// Constructor for sites whose HC colour differs from the standard + /// [`BORDER_SUBTLE_HC`]. Currently used by the WIN MOVE scrub-bar + /// marker which bumps `STATE_SUCCESS` → `STATE_SUCCESS_HC` rather + /// than to a neutral gray. + pub const fn with_hc( + default_color: bevy::prelude::Color, + hc_color: bevy::prelude::Color, + ) -> Self { + Self { default_color, hc_color } } }