feat(client_web): add right panel for status messages and dice

This commit is contained in:
Henri Bourcereau 2026-04-01 21:27:34 +02:00
parent c6031b0ace
commit 9fe79ffc7a
2 changed files with 91 additions and 70 deletions

View file

@ -54,7 +54,6 @@ input[type="text"] {
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
width: 100%; width: 100%;
max-width: 900px;
} }
/* ── Language switcher ──────────────────────────────────────────────── */ /* ── Language switcher ──────────────────────────────────────────────── */
@ -108,7 +107,6 @@ input[type="text"] {
font-size: 0.9rem; font-size: 0.9rem;
box-shadow: 0 1px 4px rgba(0,0,0,0.2); box-shadow: 0 1px 4px rgba(0,0,0,0.2);
width: 100%; width: 100%;
max-width: 900px;
} }
.player-score-header { .player-score-header {
@ -180,6 +178,28 @@ input[type="text"] {
padding-top: 0.25rem; padding-top: 0.25rem;
} }
/* ── Board + side panel ─────────────────────────────────────────────── */
.board-and-panel {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 1rem;
}
.side-panel {
display: flex;
flex-direction: column;
gap: 0.75rem;
min-width: 160px;
padding-top: 0.25rem;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* ── Status bar ─────────────────────────────────────────────────────── */ /* ── Status bar ─────────────────────────────────────────────────────── */
.status-bar { .status-bar {
display: flex; display: flex;
@ -189,7 +209,7 @@ input[type="text"] {
font-weight: 500; font-weight: 500;
} }
/* ── Dice bars ──────────────────────────────────────────────────────── */ /* ── Dice bar ──────────────────────────────────────────────────────── */
.dice-bar { .dice-bar {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -157,77 +157,78 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
// ── Opponent score (above board) ───────────────────────────────── // ── Opponent score (above board) ─────────────────────────────────
<PlayerScorePanel score=opp_score jans=opp_jans is_you=false /> <PlayerScorePanel score=opp_score jans=opp_jans is_you=false />
// ── Status ─────────────────────────────────────────────────────── // ── Board + side panel ───────────────────────────────────────────
<div class="status-bar"> <div class="board-and-panel">
<span>{move || { <Board
let n = staged_moves.get().len(); view_state=vs
if is_move_stage { player_id=player_id
t_string!(i18n, select_move, n = n + 1) selected_origin=selected_origin
} else { staged_moves=staged_moves
String::from(match (&stage, is_my_turn, &turn_stage) { />
(SerStage::Ended, _, _) => t_string!(i18n, game_over),
(SerStage::PreGame, _, _) => t_string!(i18n, waiting_for_opponent),
(SerStage::InGame, true, SerTurnStage::RollDice) => t_string!(i18n, your_turn_roll),
(SerStage::InGame, true, SerTurnStage::HoldOrGoChoice) => t_string!(i18n, hold_or_go),
(SerStage::InGame, true, _) => t_string!(i18n, your_turn),
(SerStage::InGame, false, _) => t_string!(i18n, opponent_turn),
})
}
}}</span>
</div>
// ── Opponent dice (top) ────────────────────────────────────────── // ── Side panel ───────────────────────────────────────────────
{(!is_my_turn && show_dice).then(|| view! { <div class="side-panel">
<div class="dice-bar dice-bar-opponent"> // Status message
<Die value=dice.0 used=true /> <div class="status-bar">
<Die value=dice.1 used=true /> <span>{move || {
</div> let n = staged_moves.get().len();
})} if is_move_stage {
t_string!(i18n, select_move, n = n + 1)
// ── Board ──────────────────────────────────────────────────────── } else {
<Board String::from(match (&stage, is_my_turn, &turn_stage) {
view_state=vs (SerStage::Ended, _, _) => t_string!(i18n, game_over),
player_id=player_id (SerStage::PreGame, _, _) => t_string!(i18n, waiting_for_opponent),
selected_origin=selected_origin (SerStage::InGame, true, SerTurnStage::RollDice) => t_string!(i18n, your_turn_roll),
staged_moves=staged_moves (SerStage::InGame, true, SerTurnStage::HoldOrGoChoice) => t_string!(i18n, hold_or_go),
/> (SerStage::InGame, true, _) => t_string!(i18n, your_turn),
(SerStage::InGame, false, _) => t_string!(i18n, opponent_turn),
// ── Player action bar (bottom) ─────────────────────────────────── })
{is_my_turn.then(|| view! {
<div class="dice-bar dice-bar-player">
{move || {
let (d0, d1) = if is_move_stage {
matched_dice_used(&staged_moves.get(), dice)
} else {
(false, false)
};
view! {
<Die value=dice.0 used=d0 />
<Die value=dice.1 used=d1 />
}
}}
{show_roll.then(|| view! {
<button class="btn btn-primary" on:click=move |_| {
cmd_tx_roll.unbounded_send(NetCommand::Action(PlayerAction::Roll)).ok();
}>{t!(i18n, roll_dice)}</button>
})}
{show_hold_go.then(|| view! {
<button class="btn btn-primary" on:click=move |_| {
cmd_tx_go.unbounded_send(NetCommand::Action(PlayerAction::Go)).ok();
}>{t!(i18n, go)}</button>
})}
{is_move_stage.then(|| view! {
<button
class="btn btn-secondary"
disabled=move || 2 <= staged_moves.get().len()
on:click=move |_| {
selected_origin.set(None);
staged_moves.update(|v| v.push((0, 0)));
} }
>{t!(i18n, empty_move)}</button> }}</span>
</div>
// Dice (always shown when rolled, used state depends on whose turn)
{show_dice.then(|| view! {
<div class="dice-bar">
{move || {
let (d0, d1) = if is_move_stage {
matched_dice_used(&staged_moves.get(), dice)
} else {
(true, true)
};
view! {
<Die value=dice.0 used=d0 />
<Die value=dice.1 used=d1 />
}
}}
</div>
})} })}
// Action buttons
<div class="action-buttons">
{show_roll.then(|| view! {
<button class="btn btn-primary" on:click=move |_| {
cmd_tx_roll.unbounded_send(NetCommand::Action(PlayerAction::Roll)).ok();
}>{t!(i18n, roll_dice)}</button>
})}
{show_hold_go.then(|| view! {
<button class="btn btn-primary" on:click=move |_| {
cmd_tx_go.unbounded_send(NetCommand::Action(PlayerAction::Go)).ok();
}>{t!(i18n, go)}</button>
})}
{is_move_stage.then(|| view! {
<button
class="btn btn-secondary"
disabled=move || 2 <= staged_moves.get().len()
on:click=move |_| {
selected_origin.set(None);
staged_moves.update(|v| v.push((0, 0)));
}
>{t!(i18n, empty_move)}</button>
})}
</div>
</div> </div>
})} </div>
// ── Player score (below board) ──────────────────────────────────── // ── Player score (below board) ────────────────────────────────────
<PlayerScorePanel score=my_score jans=my_jans is_you=true /> <PlayerScorePanel score=my_score jans=my_jans is_you=true />