fix(ui): make outside board clickable to exit

This commit is contained in:
Henri Bourcereau 2026-05-01 18:18:45 +02:00
parent bceec1f8fe
commit 7a990eb7e9
2 changed files with 94 additions and 0 deletions

View file

@ -1401,6 +1401,26 @@ a:hover { text-decoration: underline; }
animation: exit-glow 2s ease-in-out infinite;
}
/* ── Exit sign (§8c) — circle+arrow outside the board ──────────────── */
.exit-btn {
pointer-events: none;
opacity: 0.3;
transition: opacity 0.2s, transform 0.15s;
}
.exit-btn.exit-active {
pointer-events: auto;
cursor: pointer;
opacity: 1;
animation: exit-btn-pulse 1.4s ease-in-out infinite;
}
.exit-btn.exit-active:hover {
transform: scale(1.1);
}
@keyframes exit-btn-pulse {
0%, 100% { filter: drop-shadow(0 0 3px rgba(200,160,20,0.3)); }
50% { filter: drop-shadow(0 0 9px rgba(200,160,20,0.85)); }
}
.field.jan-hovered {
--fc: rgba(190, 140, 35, 0.8) !important;
}

View file

@ -294,6 +294,14 @@ pub fn Board(
exit_field_test = |f| matches!(f, 1..=6);
}
// Show a clickable exit sign outside the board when bearing off is possible.
let has_exit_move = valid_sequences
.iter()
.any(|(m1, m2)| m1.get_to() == 0 || m2.get_to() == 0);
let show_exit_btn = all_in_exit && is_move_stage && has_exit_move;
let seqs_exit_cls = valid_sequences.clone();
let seqs_exit_click = valid_sequences.clone();
// `valid_sequences` is cloned per field (the Vec is small; Send-safe unlike Rc).
let fields_from = |nums: &[u8], is_top_row: bool| -> Vec<AnyView> {
nums.iter()
@ -583,6 +591,72 @@ pub fn Board(
.collect()
}}
</svg>
// Exit sign: circle+arrow outside the board, next to the last exit field.
// White exits to the right (top-right quarter); Black exits to the left (top-left).
{show_exit_btn.then(|| {
let (pos_style, line_x1, line_x2, head_pts): (&str, &str, &str, &str) =
if is_white {
(
"position:absolute;right:-60px;top:15px;width:50px;height:50px",
"10", "31", "23,17 32,25 23,33",
)
} else {
(
"position:absolute;left:-60px;top:15px;width:50px;height:50px",
"40", "19", "27,17 18,25 27,33",
)
};
view! {
<div
title="Exit"
style=pos_style
class=move || {
let staged = staged_moves.get();
let sel = selected_origin.get();
let active = match sel {
Some(origin) => seqs_exit_cls.is_empty()
|| valid_dests_for(&seqs_exit_cls, &staged, origin)
.iter()
.any(|&d| d == 0),
None => false,
};
if active { "exit-btn exit-active" } else { "exit-btn" }
}
on:click=move |_| {
if !is_move_stage { return; }
let staged = staged_moves.get_untracked();
if staged.len() >= 2 { return; }
let Some(origin) = selected_origin.get_untracked() else {
return;
};
let valid = seqs_exit_click.is_empty()
|| valid_dests_for(&seqs_exit_click, &staged, origin)
.iter()
.any(|&d| d == 0);
if valid {
staged_moves.update(|v| v.push((origin, 0)));
selected_origin.set(None);
}
}
>
<svg width="50" height="50" viewBox="0 0 50 50">
<circle
cx="25" cy="25" r="20"
style="fill:rgba(10,20,10,0.75);stroke:rgba(210,170,30,0.75);stroke-width:2.5"
/>
<line
x1=line_x1 y1="25" x2=line_x2 y2="25"
style="stroke:rgba(210,170,30,0.85);stroke-width:2.5;stroke-linecap:round"
/>
<polyline
points=head_pts
style="fill:none;stroke:rgba(210,170,30,0.85);stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round"
/>
</svg>
</div>
}
.into_any()
})}
</div>
<div class="zone-labels-row">
<div class="zone-label zone-label-quarter">{label_bl}</div>