Compare commits

..

3 Commits

Author SHA1 Message Date
funman300 c50eaf81f7 feat(replay): add HC bump for WIN MOVE scrub-bar marker; extend HighContrastBackground
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 <noreply@anthropic.com>
2026-05-08 18:19:00 -07:00
funman300 b44d2777ec fix(replay): centre scrub-bar notch labels on their notch ticks
The three middle scrub-bar labels (25%, 50%, 75%) previously had their
left edge anchored at the notch percentage, making them read as
"starting after" the notch. Apply the CSS translateX(-50%) pattern for
Bevy 0.18 UI: give each middle label a fixed-width container
(SCRUB_LABEL_CENTER_WIDTH = 36px), offset the container's left edge by
-width/2 via margin.left, and add Justify::Center so the text renders
centred within the container. The container's centre then coincides with
the notch line at the chosen percentage.

Endpoints (0%, 100%) keep their flush-left / flush-right anchoring
unchanged. 1275 tests pass / 0 failing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 18:14:14 -07:00
funman300 52407e7256 docs(handoff): cut v0.21.7 — B-2 replay arc closed; dim layer ships
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 18:03:32 -07:00
5 changed files with 245 additions and 151 deletions
+31 -1
View File
@@ -6,9 +6,39 @@ project follows [Semantic Versioning](https://semver.org/).
## [Unreleased] ## [Unreleased]
No threads in flight. v0.21.6 cut on 2026-05-08; CHANGELOG accumulates No threads in flight. v0.21.7 cut on 2026-05-08; CHANGELOG accumulates
the next cycle here. the next cycle here.
## [0.21.7] — 2026-05-08
Patch release closing the last major B-2 sub-piece. Through-line:
**mini-tableau preview dim layer**. The mockup's "Game Peek Band at
50 % opacity" is now implemented as a full-screen UI scrim that darkens
the card world during replay so the chrome (banner + move-log panel)
reads clearly against the scene.
### Added
- **Full-screen tableau dim layer** (`da3e542`). Spawns a
`ReplayTableauDimLayer` UI node (100 % × 100 %, 50 % opacity
black) at `Z_REPLAY_DIM = Z_REPLAY_OVERLAY 1 = 54` whenever
a replay starts; despawned alongside the banner and move-log
panel when the replay ends. Bevy's UI/world compositor means
no changes to `card_plugin` are needed — UI nodes always
render above world-space sprites regardless of `Transform.z`.
The dim layer carries no `Interaction` component (purely
visual; pointer events pass through). Adds `Z_REPLAY_DIM`
and `TABLEAU_DIM_ALPHA` constants plus two new tests:
lifecycle (spawn/despawn mirrors the floating-chip pattern)
and z-ordering invariant (`Z_REPLAY_DIM < Z_REPLAY_OVERLAY`
pinned). 1275 tests pass / 0 failing.
### Stats
- Tests: 1275 passing / 0 failing
- Clippy: clean
- Crates touched: `solitaire_engine` (replay_overlay.rs)
## [0.21.6] — 2026-05-08 ## [0.21.6] — 2026-05-08
Patch release for the post-v0.21.5 work. Through-line: Patch release for the post-v0.21.5 work. Through-line:
+74 -114
View File
@@ -1,85 +1,66 @@
# Solitaire Quest — Session Handoff # Solitaire Quest — Session Handoff
**Last updated:** 2026-05-08 — **v0.21.6 cut and tagged at **Last updated:** 2026-05-08 — **v0.21.7 cut and tagged at
`f63db76`**, working tree clean, all post-tag work pushed to `da3e542`**, working tree clean (tag pending push).
origin.
v0.21.6 is a patch release with through-line: v0.21.7 is a single-commit patch closing the last major B-2
**Move Log panel + scrub-UX polish**. v0.21.5 closed out the sub-piece: **mini-tableau preview dim layer**. A full-screen
keyboard-accelerator surface (Space / Esc / ← / →) and the `ReplayTableauDimLayer` UI node (100 % × 100 %, 50 % opacity
keybind footer; v0.21.6 builds on that with two parallel black) at `Z_REPLAY_DIM = 54` (one rung below the replay
threads — accessibility + scrub-on-hold polish for the v0.21.5 chrome at z=55) darkens the card world during replay so the
surfaces, plus a brand-new Move Log panel anchored to the banner and move-log panel read clearly against the scene —
viewport's bottom edge that gives players a 5-row recent-and- matching the mockup's "Game Peek Band at 50 % opacity" spec
upcoming move history alongside the existing top-edge banner. without touching `card_plugin`. 13 commits have now shipped
across v0.21.4v0.21.7 on the B-2 replay screen-takeover
arc; every major sub-piece is closed.
The Move Log panel is the first replay-overlay surface that Full v0.21.7 detail lives in `CHANGELOG.md` § [0.21.7]. This
*isn't* attached to the banner — it lives at a separate screen
anchor (`bottom: 0`) with its own spawn/despawn lifecycle.
Establishes the "multi-anchor replay UI" pattern that the
remaining B-2 sub-piece (mini-tableau preview) will inherit.
Six commits on the B-2 replay screen-takeover redesign arc land
here, bringing the post-v0.21.4 total to 12. The remaining B-2
piece — mini-tableau preview that dims the gameplay tableau
during replay — is the only major sub-piece still open.
Full v0.21.6 detail lives in `CHANGELOG.md` § [0.21.6]. This
file from here on focuses on what's *open* post-cut and how to file from here on focuses on what's *open* post-cut and how to
resume. resume.
## Status at pause ## Status at pause
- **HEAD locally:** see `git rev-parse HEAD`. The cut commit is - **HEAD locally:** `da3e542` (v0.21.7 commit). Tag pending —
`f63db76`; any post-cut docs edits ride on top of that. push with `git tag v0.21.7 da3e542 && git push origin v0.21.7`.
- **HEAD on origin:** matches local. v0.21.6 is fully on origin. - **HEAD on origin:** `f63db76` (v0.21.6). v0.21.7 commit
not pushed yet; a docs-only edit will ride on top before push.
- **Working tree:** clean. No WIP outstanding. - **Working tree:** clean. No WIP outstanding.
- **`artwork/` directory:** still untracked. Intentional. - **`artwork/` directory:** still untracked. Intentional.
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` - **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
clean. clean.
- **Tests:** **1273 passing / 0 failing** across the workspace. - **Tests:** **1275 passing / 0 failing** across the workspace.
Detail in `CHANGELOG.md` § [0.21.6] § Stats. Detail in `CHANGELOG.md` § [0.21.7] § Stats.
- **Tags on origin:** `v0.9.0` through `v0.21.6`. v0.21.6 is on - **Tags on origin:** `v0.9.0` through `v0.21.6`. v0.21.7
`f63db76`; v0.21.5 stays on `a2432df`; v0.21.4 stays on tag exists locally at `da3e542`; push to origin when ready.
`23ff62c`; v0.21.3 stays on `3d92a91`; v0.21.2 stays on
`f23df3b`; v0.21.1 stays on `daa655a`; v0.21.0 stays on
`04f9bf9`; v0.20.0 stays on `41a009a`.
## Since the v0.21.6 cut ## Since the v0.21.7 cut
No threads in flight. Working tree clean as of 2026-05-08. New One commit in flight (not yet pushed to origin): `da3e542`
work since the cut would land here as commit narratives; for adds the full-screen tableau dim layer. CHANGELOG and
the v0.21.6 contents themselves, see `CHANGELOG.md` § [0.21.6]. SESSION_HANDOFF updates ride on top. Push with:
```
git push origin master
git push origin v0.21.7
```
Open next-step menu (Move Log + scrub-UX + keyboard accelerator Open next-step menu (all major B-2 sub-pieces now closed):
coverage + accessibility are all complete): 1. **Polish: notch label centering.** Bevy 0.18 lacks a
1. **Mini-tableau preview** — the only remaining major B-2 clean `translate-x: -50%` primitive so the middle three
sub-piece. Mockup shows a 240 px-tall band at 50 % opacity scrub-bar labels sit slightly right-of-notch. Could use a
showing the gameplay tableau peeking through the replay child Text wrapper with computed left-margin compensation.
chrome. Implementation needs to add a settings-aware dim
overlay or alpha modulation on the tableau cards during
replay. Architectural — touches `card_plugin` rendering.
Multi-session.
2. **Move Log auto-scroll** — only relevant if the panel's
row count grows beyond the current 5-row fixed window.
Currently the prev-2 / active / next-2 layout fits all
visible content, so auto-scroll is unneeded. Becomes
relevant if a future commit expands the panel's row
capacity (e.g. 10-row scrolling list).
3. **Polish: notch label centering.** Bevy 0.18 lacks a
clean `translate-x: -50%` primitive so middle three
labels sit slightly right-of-notch. Could use a child
Text wrapper with computed left-margin compensation.
Tiny commit, requires visual review. Tiny commit, requires visual review.
4. **Polish: WIN MOVE marker HC bump.** Currently uses 2. **Polish: WIN MOVE marker HC bump.** Currently uses
`STATE_SUCCESS` lime which stays visible under HC, but a `STATE_SUCCESS` lime which stays visible under HC, but a
contrast bump under HC would make it even more legible contrast bump under HC would make it even more legible
alongside the bumped notches. Optional. alongside the bumped notches. Optional.
3. **Move Log auto-scroll** — only relevant if the panel's
row count grows beyond the current 5-row fixed window.
Currently the prev-2 / active / next-2 layout fits all
visible content, so auto-scroll is unneeded.
Recommended order: option 1 (mini-tableau preview) is the Recommended order: options 1 and 2 are tiny polish commits
big remaining piece that closes B-2 — best tackled in a that benefit from visual review. Option 3 is a non-starter
fresh session because it crosses into `card_plugin`. Options unless the panel's row capacity grows.
3 and 4 are visual polish that benefit from user review.
## Open punch list ## Open punch list
@@ -111,31 +92,18 @@ chrome migration, splash boot screen, replay-overlay banner,
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY` card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open: palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
- **Replay-overlay screen-takeover redesign.** The full mockup - *Replay-overlay screen-takeover redesign — closed 2026-05-08
(`docs/ui-mockups/replay-overlay-mobile.html`) calls for a across 13 commits (v0.21.4v0.21.7).* The full mockup
mini-tableau preview, playback controls, move-log scroll, and (`docs/ui-mockups/replay-overlay-mobile.html`) has shipped:
a WIN MOVE marker on the scrub bar. Banner-local pieces all banner chrome (v0.21.0), floating MOVE chip (v0.21.2), WIN
shipped in v0.21.0 (`c84d9f4` + `6204db8` + `54005d5` + MOVE scrub-bar marker (post-v0.21.3), playback controls /
`e080b49`); the floating MOVE chip above the focused card Space accelerator (post-v0.21.3), scrub notches + labels +
shipped in v0.21.2 (`2fb2d63`). The WIN MOVE scrub-bar marker keybind footer + ESC / ← / → accelerators + HC border
shipped post-v0.21.3 in `ab857bb` (data field) + `52befa6` (v0.21.5), Move Log panel + HC scrub track + continuous
(UI). Playback controls (pause / resume / step + Space scrub (v0.21.6), and full-screen 50 % opacity dim layer
accelerator) shipped post-v0.21.3 in `fbe48ac`. v0.21.5 (v0.21.7). Every major B-2 sub-piece is now closed. The
bundled six more commits under "replay-overlay scrubbing only remaining items are minor polish: notch-label centering
affordances + accessibility" (scrub notches + labels + and WIN MOVE HC contrast bump (see Open next-step menu).*
keybind footer + ESC and ← / → accelerators + HC border).
v0.21.6 bundled six more under "Move Log panel + scrub-UX
polish" — bottom-edge Move Log panel with prev/active/next
rows + active highlight, HC-mode coverage for the scrub
track + notches, continuous scrub on key-held arrows. Banner
height grew 60 → 76 → 92 px across two layout-changing
commits in v0.21.5; Move Log panel grew 56 → 84 → 112 px
across the v0.21.6 move-log commits. Per-commit detail in
`CHANGELOG.md` § [0.21.5] and § [0.21.6]. The only major
B-2 piece left is the mini-tableau preview — the mockup's
"Game Peek Band" at 50 % opacity. Architectural; touches
`card_plugin` rendering.
Multi-session.
- *Floating `MOVE N/M` chip above the focused card during - *Floating `MOVE N/M` chip above the focused card during
playback — closed 2026-05-08 by `2fb2d63`.* World-space playback — closed 2026-05-08 by `2fb2d63`.* World-space
`Text2d` entity sibling to the banner overlay; uses the same `Text2d` entity sibling to the banner overlay; uses the same
@@ -278,19 +246,18 @@ into a v0.21.1 / v0.22.0 cut.
``` ```
You are a senior Rust + Bevy developer working on Solitaire Quest. You are a senior Rust + Bevy developer working on Solitaire Quest.
Working directory: <Rusty_Solitaire clone path on this machine>. Working directory: <Rusty_Solitaire clone path on this machine>.
Branch: master. v0.21.6 is tagged at f63db76 (cut 2026-05-08, a Branch: master. v0.21.7 is tagged at da3e542 (cut 2026-05-08,
patch release rolling up Move Log panel + scrub-UX polish: closes the last major B-2 sub-piece: full-screen tableau dim
brand-new bottom-edge Move Log panel with prev / active / next layer — 50 % opacity black UI scrim at z=54 that darkens the
row context + active-row highlight, plus HC-mode coverage for card world during replay so the chrome reads clearly above it).
scrub track + notches and continuous scrub on key-held arrow v0.21.6 stays at f63db76, v0.21.5 at a2432df, v0.21.4 at
keys). v0.21.5 stays at a2432df, v0.21.4 at 23ff62c, v0.21.3 23ff62c, v0.21.3 at 3d92a91, v0.21.2 at f23df3b, v0.21.1 at
at 3d92a91, v0.21.2 at f23df3b, v0.21.1 at daa655a, v0.21.0 at daa655a, v0.21.0 at 04f9bf9. Working tree clean (CHANGELOG +
04f9bf9. Working tree clean. See CHANGELOG.md § [0.21.6] for SESSION_HANDOFF docs ride on top of da3e542; push pending).
full detail. See CHANGELOG.md § [0.21.7] for full detail.
State: HEAD locally — see `git rev-parse HEAD`. The cut commit State: HEAD locally — see `git rev-parse HEAD`. Workspace
is f63db76; any post-cut docs edits ride on top of that. tests: 1275 passing / 0 failing. Clippy clean.
Workspace tests: 1273 passing / 0 failing. Clippy clean.
READ FIRST (in order, before doing anything): READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — this file 1. SESSION_HANDOFF.md — this file
@@ -314,22 +281,15 @@ DECISION TO ASK THE PLAYER FIRST:
tests can't catch. Likely surfaces JNI ClipboardManager tests can't catch. Likely surfaces JNI ClipboardManager
and Android Keystore stubs that need real bridges. Larger and Android Keystore stubs that need real bridges. Larger
scope; needs an Android device or emulator running. scope; needs an Android device or emulator running.
B. Replay-overlay screen-takeover redesign — nearly complete B. Replay-overlay polish (B-2 arc fully closed in v0.21.7).
after 12 commits across v0.21.4-6. Scrub bar with notches All 13 planned sub-pieces shipped. Remaining items are
+ labels + WIN MOVE marker, pause / resume / step / stop minor polish: (a) scrub-bar notch-label centering — middle
buttons, Space + Esc + ← / → keyboard accelerators with three labels sit slightly right-of-notch due to Bevy 0.18
continuous scrub on hold, keybind-hint footer, full HC-mode lacking `translate-x: -50%`; tiny commit, needs visual
coverage on the banner pieces, and a brand-new bottom-edge review. (b) WIN MOVE marker HC contrast bump — optional
Move Log panel with a 5-row prev/active/next window all luminance boost under HC mode. Both are single commits
ship. The only remaining major B-2 sub-piece is the requiring visual review; recommend treating as a v0.21.8
**mini-tableau preview** — the mockup's "Game Peek Band" polish pass after manual testing.
at 50 % opacity showing the tableau through the replay
chrome. Implementation needs a settings-aware dim overlay
or alpha modulation on the tableau cards during replay.
Architectural — touches `card_plugin` rendering. Best
tackled in a fresh session because it crosses into a
plugin the recent B-2 work hasn't touched. Mockup at
`docs/ui-mockups/replay-overlay-mobile.html`.
C. Phase 8 (sync) — local storage scaffolding, self-hosted C. Phase 8 (sync) — local storage scaffolding, self-hosted
Axum server, `SolitaireServerClient` impl, GPGS stub Axum server, `SolitaireServerClient` impl, GPGS stub
wired into Settings. The biggest open arc by scope; rolls wired into Settings. The biggest open arc by scope; rolls
+104 -28
View File
@@ -38,8 +38,8 @@ use solitaire_data::ReplayMove;
use crate::ui_modal::{spawn_modal_button, ButtonVariant}; use crate::ui_modal::{spawn_modal_button, ButtonVariant};
use crate::ui_theme::{ use crate::ui_theme::{
ACCENT_PRIMARY, BG_ELEVATED_HI, BORDER_SUBTLE, HighContrastBackground, HighContrastBorder, ACCENT_PRIMARY, BG_ELEVATED_HI, BORDER_SUBTLE, HighContrastBackground, HighContrastBorder,
STATE_SUCCESS, TEXT_PRIMARY, TEXT_PRIMARY_HC, TEXT_SECONDARY, TYPE_BODY, TYPE_CAPTION, STATE_SUCCESS, STATE_SUCCESS_HC, TEXT_PRIMARY, TEXT_PRIMARY_HC, TEXT_SECONDARY, TYPE_BODY,
TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_4, Z_DROP_OVERLAY, TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_4, Z_DROP_OVERLAY,
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -105,6 +105,21 @@ const SCRUB_LABEL_ROW_HEIGHT: f32 = 16.0;
/// (12 px) + 4 px breathing room. /// (12 px) + 4 px breathing room.
const KEYBIND_FOOTER_HEIGHT: f32 = 16.0; const KEYBIND_FOOTER_HEIGHT: f32 = 16.0;
/// Fixed pixel width of the centred scrub-bar notch-label container.
/// Wide enough to hold the widest label ("100%" at 4 chars) while
/// narrower than the 25 % gap between adjacent notches (≈ banner_w
/// × 0.25; on a 320 px banner that's 80 px). A 36 px container
/// leaves ≥ 44 px of clearance on each side at the narrowest common
/// screen width.
///
/// Container width drives the `margin.left = -width / 2` centering
/// trick: the container's left edge is placed at `left: Percent(pct)`
/// and then shifted left by half its own width, so the container's
/// centre coincides with the notch line. `Justify::Center` then
/// renders the text centred within the container. This is the
/// CSS `translateX(-50%)` pattern adapted for Bevy 0.18 UI.
const SCRUB_LABEL_CENTER_WIDTH: f32 = 36.0;
/// How long a held arrow key waits before firing the next repeat /// How long a held arrow key waits before firing the next repeat
/// step. 100 ms = 10 steps/sec — fast enough to scrub through a /// step. 100 ms = 10 steps/sec — fast enough to scrub through a
/// hundred-move replay in ~10 seconds while held, slow enough that /// hundred-move replay in ~10 seconds while held, slow enough that
@@ -764,6 +779,11 @@ fn spawn_overlay(
..default() ..default()
}, },
BackgroundColor(STATE_SUCCESS), 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 // Fixed quarter-mark notches: five 1px vertical
@@ -822,45 +842,63 @@ fn spawn_overlay(
labels.iter().zip(positions.iter()).enumerate() labels.iter().zip(positions.iter()).enumerate()
{ {
// Endpoints flush to the row's edges; middle // Endpoints flush to the row's edges; middle
// three labels anchor at their percentage. // three labels use the `translateX(-50%)`
// `i == 0` → flush left (`left: 0`), so the // pattern for Bevy 0.18 UI: a fixed-width
// "0%" caption doesn't get clipped at the // container is placed at `left: Percent(pct)`
// left edge. `i == last` → flush right // then shifted left by half its own width via
// (`right: 0`) so "100%" doesn't overflow // `margin.left: Px(-SCRUB_LABEL_CENTER_WIDTH/2)`.
// the banner. Bevy 0.18 UI has no clean // `Justify::Center` renders the text centred
// CSS-style `translate-x: -50%` centering, // within the container so the text's visual
// so the middle three labels sit slightly // centre coincides with the notch line.
// right-of-notch — visually subtle at this let (node, justify) = if i == 0 {
// font size; explicit polish target if (
// anyone notices. Node {
let mut node = Node { position_type: PositionType::Absolute,
position_type: PositionType::Absolute, top: Val::Px(2.0),
top: Val::Px(2.0), left: Val::Px(0.0),
..default() ..default()
}; },
if i == 0 { Justify::Left,
node.left = Val::Px(0.0); )
} else if i == labels.len() - 1 { } else if i == labels.len() - 1 {
node.right = Val::Px(0.0); (
Node {
position_type: PositionType::Absolute,
top: Val::Px(2.0),
right: Val::Px(0.0),
..default()
},
Justify::Right,
)
} else { } else {
node.left = Val::Percent(*pct); (
} Node {
position_type: PositionType::Absolute,
top: Val::Px(2.0),
left: Val::Percent(*pct),
width: Val::Px(SCRUB_LABEL_CENTER_WIDTH),
margin: UiRect {
left: Val::Px(-SCRUB_LABEL_CENTER_WIDTH / 2.0),
..default()
},
..default()
},
Justify::Center,
)
};
row.spawn(( row.spawn((
ReplayOverlayScrubNotchLabel, ReplayOverlayScrubNotchLabel,
node, node,
Text::new(*label), Text::new(*label),
TextLayout::new_with_justify(justify),
TextFont { TextFont {
font: font_handle_for_labels.clone(), font: font_handle_for_labels.clone(),
font_size: TYPE_CAPTION, font_size: TYPE_CAPTION,
..default() ..default()
}, },
// The mockup's `text-outline` (BORDER_SUBTLE)
// would match the notches but reads as too
// low-contrast against `BG_ELEVATED_HI` for
// the labels to actually be legible at 12 px.
// TEXT_SECONDARY keeps the subdued visual // TEXT_SECONDARY keeps the subdued visual
// hierarchy (caption, not headline) while // hierarchy (caption, not headline) while
// staying readable. // staying readable against BG_ELEVATED_HI.
TextColor(TEXT_SECONDARY), TextColor(TEXT_SECONDARY),
)); ));
} }
@@ -2316,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<ReplayOverlayWinMoveMarker>>();
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 // scrub_notch_positions + ReplayOverlayScrubNotch spawn behaviour
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
+1 -1
View File
@@ -701,7 +701,7 @@ pub(crate) fn update_high_contrast_backgrounds(
let high_contrast = settings.0.high_contrast_mode; let high_contrast = settings.0.high_contrast_mode;
for (marker, mut bg) in backgrounds.iter_mut() { for (marker, mut bg) in backgrounds.iter_mut() {
let target = if high_contrast { let target = if high_contrast {
BORDER_SUBTLE_HC marker.hc_color
} else { } else {
marker.default_color marker.default_color
}; };
+35 -7
View File
@@ -93,6 +93,13 @@ pub const ACCENT_SECONDARY: Color = Color::srgb(0.882, 0.639, 0.933);
/// from base16-eighties. `#acc267`. /// from base16-eighties. `#acc267`.
pub const STATE_SUCCESS: Color = Color::srgb(0.675, 0.761, 0.404); 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 /// Warning — penalty signal, daily-seed expiry countdown, sync-pending
/// status. Gold from base16-eighties. **Both** Undo and Recycle /// status. Gold from base16-eighties. **Both** Undo and Recycle
/// counters use this when non-zero. `#ddb26f`. /// 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 /// often render as tiny full-bleed `Node`s, not as borders, so the
/// border-marker pattern doesn't apply. /// border-marker pattern doesn't apply.
/// ///
/// `default_color` records the off-state colour the entity was /// `default_color` records the off-state colour; `hc_color` the on-
/// spawned with so the system can revert when HC is toggled back /// state colour. [`with_default`] fills `hc_color` with
/// off. The accompanying paint system is /// [`BORDER_SUBTLE_HC`] so the 90 % of sites that just need the
/// [`update_high_contrast_backgrounds`](crate::settings_plugin::update_high_contrast_backgrounds). /// 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 /// [`BackgroundColor`]: bevy::prelude::BackgroundColor
#[derive(bevy::prelude::Component, Debug, Clone, Copy)] #[derive(bevy::prelude::Component, Debug, Clone, Copy)]
pub struct HighContrastBackground { pub struct HighContrastBackground {
/// Background colour to use when high-contrast mode is *off* — /// Background colour to use when high-contrast mode is *off* —
/// the site's normal idle / active-state colour. /// the site's normal idle / active-state colour.
pub default_color: bevy::prelude::Color, 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 { impl HighContrastBackground {
/// Convenience constructor — /// Convenience constructor — HC colour defaults to
/// `HighContrastBackground::with_default(BORDER_SUBTLE)`. /// [`BORDER_SUBTLE_HC`].
pub const fn with_default(default_color: bevy::prelude::Color) -> Self { 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 }
} }
} }