ui: unified rounded navbar everywhere, vertical home actions, no bot disconnect spam
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 8m9s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m10s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 57s

- NavRail: one rounded "pill" tab bar on every screen (matches home). ScreenShell
  lays out as a portrait column and floats the nav with margins + safe-area;
  dropped the landscape side-rail variant.
- Home: the three mode cards now stack vertically as full-width rows (portrait
  friendly) instead of a 3-up landscape row.
- Disconnect: removed the simulated random opponent "disconnect" in local games
  (DISCONNECT_CHANCE) and the in-game DisconnectBanner — bots/filled seats just
  auto-play their turn; no message, no pause. (Live reconnect grace still tracked
  internally but no longer shows a banner.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-12 01:12:26 +03:30
parent 55c0407d73
commit a7c0900c3b
5 changed files with 39 additions and 86 deletions
+5 -24
View File
@@ -32,10 +32,8 @@ export const TIMING = {
/** Base turn time (starter league / vs-AI). Higher leagues use less — see
* `turnMsForStake`. Kept for reference; scheduling derives the real value. */
export const TURN_MS = 15000;
/** Grace period to wait for a disconnected player to return. */
/** Grace period to wait for a disconnected player to return (live games). */
export const RECONNECT_MS = 15000;
/** Per-turn chance an online opponent briefly drops (mock). */
const DISCONNECT_CHANCE = 0.07;
export type GameMode = "ai" | "online";
@@ -267,27 +265,10 @@ export const useGameStore = create<GameStore>((set, get) => {
scheduleAuto();
}, turnMs);
} else {
const st = get();
if (
st.mode === "online" &&
st.disconnectedSeat == null &&
Math.random() < DISCONNECT_CHANCE
) {
// simulate this opponent dropping; wait for them, then they return
set({
turnDeadline: null,
disconnectedSeat: seat,
reconnectDeadline: Date.now() + RECONNECT_MS,
});
const back = Math.floor(RECONNECT_MS * (0.4 + Math.random() * 0.45));
pending = setTimeout(() => {
set({ disconnectedSeat: null, reconnectDeadline: null });
playSeatAI(seat);
}, back);
} else {
set({ turnDeadline: null });
pending = setTimeout(() => playSeatAI(seat), fast(TIMING.aiPlay));
}
// Opponents (bots / filled seats) simply auto-play their turn — no
// simulated disconnects, no pause, no banner.
set({ turnDeadline: null });
pending = setTimeout(() => playSeatAI(seat), fast(TIMING.aiPlay));
}
break;
}