diff --git a/web/src/components/BattlePhase.tsx b/web/src/components/BattlePhase.tsx
index 00cf400..75a1c4f 100644
--- a/web/src/components/BattlePhase.tsx
+++ b/web/src/components/BattlePhase.tsx
@@ -289,7 +289,11 @@ function unitPop(ev: BattleEvent | null, seat: number, events: BattleEvent[], st
export function BattlePhase({ view, send, step, setStep }: Props) {
const battle = view.battle!
const events = battle.events ?? []
+ // acked = the player committed to the next round (waiting on the opponent).
const [acked, setAcked] = useState(false)
+ // The result dialog pops when the replay ends. "Not yet" dismisses it so the
+ // battle can be rewatched; a toolbar button then remains to advance the round.
+ const [resultDismissed, setResultDismissed] = useState(false)
// The deck-peek popover lives inside .battlefield, which sets overflow-x
// (and thus overflow-y) to auto — so an absolutely-positioned popover gets
// clipped to the battlefield. We anchor it to the hovered stack's viewport
@@ -358,6 +362,17 @@ export function BattlePhase({ view, send, step, setStep }: Props) {
const won = battle.winnerSeat === youSeat
const draw = battle.winnerSeat < 0
+ // Commit to the next round: ack and hand off to the server.
+ const proceed = () => {
+ setAcked(true)
+ send({ type: 'ready' })
+ }
+ const nextLabel = battle.round >= view.maxRounds ? 'See final results' : 'Next round →'
+ // Show the result dialog when the replay finishes — unless the player chose
+ // "Not yet" to rewatch (then only the toolbar button remains). Once they've
+ // committed, the dialog reappears to show the "waiting" state.
+ const showResult = done && (acked || !resultDismissed)
+
// Dice for the rock event currently on screen. renderSide draws them under
// the throwing side's deck; `step` keys the tray so the scramble replays for
// every rock event.
@@ -603,6 +618,17 @@ export function BattlePhase({ view, send, step, setStep }: Props) {
>
⏱ {speed}×
+ {/* Once the replay ends, a persistent way to advance the round — the
+ path forward after the result dialog is dismissed to rewatch. */}
+ {done && !acked && (
+
+ )}
@@ -658,33 +684,39 @@ export function BattlePhase({ view, send, step, setStep }: Props) {
)
})()}
- {done && (
-
- {view.players[battle.winnerSeat]?.name} wins{' '}
- {'🏆'.repeat(battle.trophies)}
+ {/* The result lands as a centered dialog — always fully on screen, even on
+ a phone where the arena fills the viewport. "Not yet" dismisses it so
+ the battle can be rewatched; the toolbar keeps a way to advance. */}
+ {showResult &&
+ createPortal(
+