diff --git a/clients/web/assets/style.css b/clients/web/assets/style.css index 35d4eee..a129c91 100644 --- a/clients/web/assets/style.css +++ b/clients/web/assets/style.css @@ -705,10 +705,6 @@ a:hover { text-decoration: underline; } font-size: 1.05rem; color: var(--ui-ink); letter-spacing: 0.02em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; } .score-bars { display: flex; flex-direction: row; gap: 1.5rem; flex: 1; align-items: center; } @@ -812,7 +808,7 @@ a:hover { text-decoration: underline; } } .score-row-name { - width: 120px; + min-width: 120px; flex-shrink: 0; display: flex; align-items: baseline; @@ -1526,72 +1522,13 @@ a:hover { text-decoration: underline; } .field.clickable { cursor: pointer; + --fc: #8fc840 !important; } -.field.clickable:hover { - --fc: rgba(200,170,50,0.18) !important; -} +.field.clickable:hover { --fc: #74aa28 !important; } .field.selected { - /* natural triangle color; tab is the indicator */ -} - -/* ── Tab indicators: small markers at the field's wide base ──────── */ -/* Bot-row: tabs hang below; top-row: tabs hang above. */ -/* The tab sits at ≈ -6px which lands on the board's wooden rail. */ - -.field.clickable::after, -.field.selected::after { - content: ''; - position: absolute; - left: 50%; - transform: translateX(-50%); - width: 22px; - height: 8px; - pointer-events: none; - z-index: 2; -} - -.bot-row .field.clickable::after, -.bot-row .field.selected::after { - bottom: -6px; - top: auto; - border-radius: 0 0 10px 10px; -} -.top-row .field.clickable::after, -.top-row .field.selected::after { - top: -6px; - bottom: auto; - border-radius: 10px 10px 0 0; -} - -/* Possible origin: hollow gold outline */ -.field.clickable:not(.dest):not(.selected)::after { - background: rgba(210,170,30,0.15); - border: 1.5px solid rgba(210,170,30,0.75); - box-shadow: 0 0 4px rgba(210,170,30,0.3); -} - -/* Selected origin: filled amber, breathing glow */ -.field.selected::after { - background: linear-gradient(to bottom, #e8b020, #c07808); - border: 1px solid rgba(255,225,65,0.55); - animation: tab-pulse 1.2s ease-in-out infinite; -} - -@keyframes tab-pulse { - 0%, 100% { box-shadow: 0 0 5px rgba(220,155,15,0.55), 0 0 2px rgba(255,220,50,0.3); } - 50% { box-shadow: 0 0 13px rgba(240,178,22,0.88), 0 0 6px rgba(255,230,60,0.6); } -} - -/* Valid destination: soft ivory/pearl */ -.field.clickable.dest:not(.selected)::after { - background: rgba(240,230,205,0.88); - border: 1.5px solid rgba(190,165,105,0.65); - box-shadow: 0 0 3px rgba(190,165,105,0.2); -} -.field.clickable.dest:not(.selected):hover::after { - background: rgba(228,210,162,0.95); - border-color: rgba(210,175,40,0.72); - box-shadow: 0 0 7px rgba(210,175,40,0.42); + --fc: #5a8a18 !important; + outline: 2px solid rgba(255,255,255,0.3); + outline-offset: -2px; } .field-num { diff --git a/clients/web/src/app.rs b/clients/web/src/app.rs index 85ee622..de8d55f 100644 --- a/clients/web/src/app.rs +++ b/clients/web/src/app.rs @@ -45,9 +45,6 @@ pub struct GameUiState { pub my_scored_event: Option, pub opp_scored_event: Option, pub last_moves: Option<(CheckerMove, CheckerMove)>, - /// True on the echo screen state set alongside a pending item — suppresses dice - /// roll animation and sound since they already played on the pending screen. - pub suppress_dice_anim: bool, } /// Reason the UI is paused waiting for the player to click Continue. @@ -355,7 +352,6 @@ pub fn App() -> impl IntoView { my_scored_event: None, opp_scored_event: None, last_moves: compute_last_moves(&prev_vs, &vs, is_own_move), - suppress_dice_anim: false, }, pending, screen, @@ -406,16 +402,13 @@ fn GameOverlay( ) -> impl IntoView { let location = use_location(); - // Memoize the front of the pending queue so that pushing a new item to the back - // does not re-mount GameScreen (and replay dice animation/sound) when the displayed - // state (the front) hasn't changed. - let pending_front = Memo::new(move |_| pending.with(|q| q.front().cloned())); - move || { if location.pathname.get() != "/" { return view! {}.into_any(); } - if let Some(state) = pending_front.get() { + let q = pending.get(); + let front = q.front().cloned(); + if let Some(state) = front { return view! {
} diff --git a/clients/web/src/game/components/board.rs b/clients/web/src/game/components/board.rs index 02bee6a..b5f6c8f 100644 --- a/clients/web/src/game/components/board.rs +++ b/clients/web/src/game/components/board.rs @@ -272,9 +272,6 @@ pub fn Board( /// Fields where a hit (battue) was scored this turn — show ripple animation. #[prop(default = vec![])] hit_fields: Vec, - /// Suppress dice animation (echo screen shown after a pending confirm was dismissed). - #[prop(default = false)] - suppress_dice_anim: bool, ) -> impl IntoView { let board = view_state.board; let white_points = view_state.scores[0].points; @@ -286,11 +283,6 @@ pub fn Board( view_state.turn_stage, SerTurnStage::Move | SerTurnStage::HoldOrGoChoice ); - // True when ANY player is in the Move/HoldOrGoChoice stage — i.e., dice are fresh for the active player. - let active_is_move_stage = matches!( - view_state.turn_stage, - SerTurnStage::Move | SerTurnStage::HoldOrGoChoice - ); let is_white = player_id == 0; let hovered_moves = use_context::>>(); @@ -385,7 +377,7 @@ pub fn Board( cls.push_str(" exit-eligible"); } - if seqs_c.is_empty() && !is_move_stage { + if seqs_c.is_empty() { // No restriction (dice not rolled or not move stage) if can_stage && (sel.is_some() || is_mine) { cls.push_str(" clickable"); @@ -541,13 +533,8 @@ pub fn Board( bar_matched_dice_used(&staged, dice_vals) } else if is_my_turn { (true, true) - } else if active_is_move_stage && !suppress_dice_anim { - // Opponent has fresh dice in their Move stage (first view). - (false, false) } else { - // Dice are old: either from the previous turn (opponent not yet - // rolled) or this is the echo screen after a pending confirm. - (true, true) + (false, false) }; let used = if die_idx == 0 { u0 } else { u1 }; view! { } diff --git a/clients/web/src/game/components/game_screen.rs b/clients/web/src/game/components/game_screen.rs index 108208a..d55284f 100644 --- a/clients/web/src/game/components/game_screen.rs +++ b/clients/web/src/game/components/game_screen.rs @@ -29,7 +29,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { ); let waiting_for_confirm = state.waiting_for_confirm; let pause_reason = state.pause_reason.clone(); - let suppress_dice_anim = state.suppress_dice_anim; // ── Hovered jan moves (shown as arrows on the board) ────────────────────── let hovered_jan_moves: RwSignal> = RwSignal::new(vec![]); @@ -207,14 +206,8 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { }; // ── Sound effects (fire once on mount = once per state snapshot) ────────── - // Dice roll: dice are fresh for the currently active player (Move stage means - // someone just rolled). Skipped on turn-switch states where the old dice linger - // in RollDice/MarkPoints stage before the opponent has rolled. - let active_is_move_stage = matches!( - vs.turn_stage, - SerTurnStage::Move | SerTurnStage::HoldOrGoChoice - ); - if show_dice && last_moves.is_none() && active_is_move_stage && !suppress_dice_anim { + // Dice roll: dice just appeared (no preceding moves in this snapshot). + if show_dice && last_moves.is_none() { crate::game::sound::play_dice_roll(); } // Checker move: moves were committed in the preceding action. @@ -335,7 +328,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { bar_is_double=is_double_dice last_moves=last_moves hit_fields=hit_fields - suppress_dice_anim=suppress_dice_anim /> // ── Status, hints, and actions — cream strip below board ─ diff --git a/clients/web/src/game/session.rs b/clients/web/src/game/session.rs index b3704ec..df603ec 100644 --- a/clients/web/src/game/session.rs +++ b/clients/web/src/game/session.rs @@ -47,7 +47,6 @@ pub async fn run_local_bot_game( my_scored_event: None, opp_scored_event: None, last_moves: None, - suppress_dice_anim: false, })); use futures::StreamExt; @@ -74,7 +73,6 @@ pub async fn run_local_bot_game( my_scored_event: scored, opp_scored_event: opp_scored, last_moves: compute_last_moves(&prev_vs, &vs, true), - suppress_dice_anim: false, })); } Some(NetCommand::PlayVsBot) => return true, @@ -104,7 +102,6 @@ pub async fn run_local_bot_game( my_scored_event: None, opp_scored_event: None, last_moves: compute_last_moves(&delta_prev_vs, &vs, false), - suppress_dice_anim: false, }, pending, screen, @@ -223,7 +220,6 @@ pub fn push_or_show( }); screen.set(Screen::Playing(GameUiState { last_moves: None, - suppress_dice_anim: true, ..new_state })); } else {