Compare commits
No commits in common. "ec0a3b0ee1cc38ceac4ab06c2112161ba67d6070" and "236c6df826698879b0e19db3d2f4daed42721a67" have entirely different histories.
ec0a3b0ee1
...
236c6df826
5 changed files with 13 additions and 108 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -45,9 +45,6 @@ pub struct GameUiState {
|
|||
pub my_scored_event: Option<ScoredEvent>,
|
||||
pub opp_scored_event: Option<ScoredEvent>,
|
||||
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! {
|
||||
<div class="game-overlay"><GameScreen state /></div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<u8>,
|
||||
/// 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::<RwSignal<Vec<(CheckerMove, CheckerMove)>>>();
|
||||
|
||||
|
|
@ -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! { <Die value=die_val used=used is_double=bar_is_double /> }
|
||||
|
|
|
|||
|
|
@ -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<Vec<(CheckerMove, CheckerMove)>> = 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 ─
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue