Battle result dialog, speed toggle, and topbar refactor for mobile.
- End-of-battle result is now a centered dialog (Victory/Defeat/Draw) with 'Next round' and 'Not yet'; 'Not yet' dismisses it to rewatch the battle, and a toolbar button remains to advance. A modal is always fully on screen, fixing the next-round button being unreachable below the arena on phones. - Also make the play area genuinely scrollable on phones (stretch the body so table-main's overflow can engage) as a safety net. - Collapse the 5-button playback speed picker into one button that cycles through the options, keeping the battle controls on a single row. - Topbar: show the round as a die face (1-6 pips) on all screens, move the room code and Leave into a '...' dropdown menu, and drop the wordmark on phones so the rail stays one row (die + players + menu). - On phones, strip battle cards to art + power/damage + name (no suit, tier, or ability text); the magnified view still shows the full card.
This commit is contained in:
@@ -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}×
|
||||
</button>
|
||||
{/* Once the replay ends, a persistent way to advance the round — the
|
||||
path forward after the result dialog is dismissed to rewatch. */}
|
||||
{done && !acked && (
|
||||
<button
|
||||
className="btn btn-primary btn-sm battle-next"
|
||||
onClick={proceed}
|
||||
title={nextLabel}
|
||||
>
|
||||
{battle.round >= view.maxRounds ? 'Results →' : 'Next round →'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -658,33 +684,39 @@ export function BattlePhase({ view, send, step, setStep }: Props) {
|
||||
)
|
||||
})()}
|
||||
|
||||
{done && (
|
||||
<div className={`battle-result ${draw ? 'is-draw' : won ? 'is-win' : 'is-loss'}`}>
|
||||
<div className="battle-result-title">
|
||||
{draw ? 'Draw!' : won ? 'Victory!' : 'Defeat…'}
|
||||
</div>
|
||||
{!draw && (
|
||||
<div className="battle-result-sub">
|
||||
{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(
|
||||
<div className="modal-backdrop">
|
||||
<div className={`modal battle-result-modal ${draw ? 'is-draw' : won ? 'is-win' : 'is-loss'}`}>
|
||||
<div className="battle-result-title">
|
||||
{draw ? 'Draw!' : won ? 'Victory!' : 'Defeat…'}
|
||||
</div>
|
||||
{!draw ? (
|
||||
<div className="battle-result-sub">
|
||||
{view.players[battle.winnerSeat]?.name} wins {'🏆'.repeat(battle.trophies)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="battle-result-sub">No trophies awarded</div>
|
||||
)}
|
||||
{acked ? (
|
||||
<p className="muted">Waiting for opponent…</p>
|
||||
) : (
|
||||
<div className="actions">
|
||||
<button className="btn btn-ghost" onClick={() => setResultDismissed(true)}>
|
||||
Not yet
|
||||
</button>
|
||||
<button className="btn btn-primary btn-big" onClick={proceed}>
|
||||
{nextLabel}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{draw && <div className="battle-result-sub">No trophies awarded</div>}
|
||||
{acked ? (
|
||||
<p className="muted">Waiting for opponent…</p>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-primary btn-big"
|
||||
onClick={() => {
|
||||
setAcked(true)
|
||||
send({ type: 'ready' })
|
||||
}}
|
||||
>
|
||||
{battle.round >= view.maxRounds ? 'See final results' : 'Next round →'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user