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