diff --git a/client_web/assets/style.css b/client_web/assets/style.css index 898cc0f..3691894 100644 --- a/client_web/assets/style.css +++ b/client_web/assets/style.css @@ -450,8 +450,8 @@ body { strip after a few seconds, and reveal fully on hover. */ .side-panel { position: absolute; - right: 0; - top: 0; + right: -8px; + top: 10px; z-index: 20; display: flex; flex-direction: column; @@ -645,8 +645,7 @@ body { /* ── Wrapper: handles slide-in → peek → reveal lifecycle ────────────── The wrapper starts off-screen right (translateX(100%)), slides in on mount via animation, then Leptos adds .peeked after 3.4s to slide it - back to a 28px peek strip. First hover adds .revealed for permanent - visibility. pointer-events: auto overrides the parent's none. */ + back to a 28px peek strip. */ @keyframes scoring-panel-enter { from { transform: translateX(100%); } to { transform: translateX(0); } @@ -662,7 +661,7 @@ body { /* Peeked: slide right by the full panel width so the board is 100% clear. The panel's left portion stays visible in whatever free space exists to - the right of the board (depends on viewport width). */ + the right of the board. */ .scoring-panel-wrapper.peeked { transform: translateX(100%); } @@ -868,8 +867,8 @@ body { .board-quarter .field.zone-retour:nth-child(odd) { --fc: #6a2810; } .board-quarter .field.zone-retour:nth-child(even) { --fc: #f2dfa0; } -/* ── Rest corner (§3) — before .clickable so green wins when interactive ── */ -.field.corner { --fc: var(--field-corner) !important; } +/* ── Rest corner — before .clickable so green wins when interactive ── */ +/* .field.corner { --fc: var(--field-corner) !important; } */ /* Crown glyph sits behind checkers (z-index:-1) so it shows only on empty corners */ .field.corner::after { diff --git a/client_web/src/components/board.rs b/client_web/src/components/board.rs index 9ab94ae..0610c86 100644 --- a/client_web/src/components/board.rs +++ b/client_web/src/components/board.rs @@ -256,6 +256,7 @@ pub fn Board( /// Whether we're in the move stage (determines used/unused die appearance). #[prop(default = false)] bar_is_move: bool, + #[prop(default = false)] is_my_turn: bool, /// Whether the dice are a double (golden glow). #[prop(default = false)] bar_is_double: bool, @@ -344,6 +345,9 @@ pub fn Board( cls.push_str(" corner-available"); } } + if is_rest_corner(field_num, !is_white) { + cls.push_str(" corner"); + } if all_in_exit && exit_field_test(field_num) { cls.push_str(" exit-eligible"); } @@ -501,8 +505,10 @@ pub fn Board( let staged = staged_moves.get(); let (u0, u1) = if bar_is_move { bar_matched_dice_used(&staged, dice_vals) - } else { + } else if is_my_turn { (true, true) + } else { + (false, false) }; let used = if die_idx == 0 { u0 } else { u1 }; view! { } diff --git a/client_web/src/components/game_screen.rs b/client_web/src/components/game_screen.rs index 2fa14c3..072b827 100644 --- a/client_web/src/components/game_screen.rs +++ b/client_web/src/components/game_screen.rs @@ -37,8 +37,8 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let cmd_tx = use_context::>() .expect("UnboundedSender not found in context"); - let pending = use_context::>>() - .expect("pending not found in context"); + let pending = + use_context::>>().expect("pending not found in context"); let cmd_tx_effect = cmd_tx.clone(); Effect::new(move |_| { let moves = staged_moves.get(); @@ -68,7 +68,9 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { if show_roll && !waiting_for_confirm { let cmd_tx_auto = cmd_tx.clone(); Effect::new(move |_| { - cmd_tx_auto.unbounded_send(NetCommand::Action(PlayerAction::Roll)).ok(); + cmd_tx_auto + .unbounded_send(NetCommand::Action(PlayerAction::Roll)) + .ok(); }); } @@ -92,13 +94,19 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let mut store_board = StoreBoard::new(); store_board.set_positions(&Color::White, vs.board); let store_dice = StoreDice { values: dice }; - let color = if player_id == 0 { Color::White } else { Color::Black }; + let color = if player_id == 0 { + Color::White + } else { + Color::Black + }; let rules = MoveRules::new(&color, &store_board, store_dice); let raw = rules.get_possible_moves_sequences(true, vec![]); if player_id == 0 { raw } else { - raw.into_iter().map(|(m1, m2)| (m1.mirror(), m2.mirror())).collect() + raw.into_iter() + .map(|(m1, m2)| (m1.mirror(), m2.mirror())) + .collect() } } else { vec![] @@ -113,7 +121,8 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { // ── Scoring notifications ────────────────────────────────────────────────── let my_scored_event = state.my_scored_event.clone(); let opp_scored_event = state.opp_scored_event.clone(); - let hole_toast_info = my_scored_event.as_ref() + let hole_toast_info = my_scored_event + .as_ref() .filter(|e| e.holes_gained > 0) .map(|e| (e.holes_total, e.bredouille)); @@ -123,14 +132,16 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { // §6e — fields where a battue (hit) was scored; ripple animation shown there. let hit_fields: Vec = { - let is_hit_jan = |jan: &Jan| matches!( - jan, - Jan::TrueHitSmallJan - | Jan::TrueHitBigJan - | Jan::TrueHitOpponentCorner - | Jan::FalseHitSmallJan - | Jan::FalseHitBigJan - ); + let is_hit_jan = |jan: &Jan| { + matches!( + jan, + Jan::TrueHitSmallJan + | Jan::TrueHitBigJan + | Jan::TrueHitOpponentCorner + | Jan::FalseHitSmallJan + | Jan::FalseHitBigJan + ) + }; let mut fields: Vec = vec![]; for event_opt in [&my_scored_event, &opp_scored_event] { if let Some(event) = event_opt { @@ -148,9 +159,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { } } } - if !fields.is_empty() { - leptos::logging::log!("[6e] hit_fields = {:?}", fields); - } fields }; @@ -248,6 +256,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { valid_sequences=valid_sequences bar_dice=show_dice.then_some(dice) bar_is_move=is_move_stage + is_my_turn=is_my_turn bar_is_double=is_double_dice last_moves=last_moves hit_fields=hit_fields