Feature: public rooms, mobile UX, reconnection, and gameplay fixes
Rooms & lobby
- Rename docker-compose.yml → compose.yml
- Public/Private toggle on room creation; public rooms assign random seats
to prevent team collusion
- GET /api/rooms API — lists open public rooms; Join tab shows live list
with one-tap join
- Room creator: swap any two seats by tapping (select-to-swap UI); ▶ Start
Game button force-starts with bots filling empty seats
Reconnection
- Session moved from sessionStorage → localStorage (survives browser close)
- Socket handlers split: socket.once for one-shot callbacks, persistent
socket.on('connect') for auto-rejoin on network drops
- Server rejoin accepts userId match as fallback (cross-device rejoin for
authenticated users); re-issues token on success
- Server emits hasActiveGame on connect so auth'd users on a new device are
pulled back into their game automatically
- Explicit leave nulls seat/token/userIds so hasActiveGame never re-drags a
player back in after they chose to leave
Mobile UX
- Remove all opponent/partner card backs; replace with compact card-count
badge — frees ~120px of vertical space on small phones
- Screen height: 100dvh (dynamic viewport) instead of 100vh — fixes the
"only top 1/5 visible" issue on phones with browser chrome
- Table grid side columns shrunk to 36px on touch devices; player names
rotated vertically
- Bidding overlay: transparent non-blocking top panel on touch; hand stays
visible and interactive; auto fan-mode during bidding
- touch-action: pan-x on hand scroll, none in fan/drag mode — suppresses
Android back-gesture and Google Gemini conflicts
- user-select: none on game screen prevents long-press selection menus
Gameplay & notifications
- Center trick area now shows whose turn it is instead of trump (trump is
already in the info bar); flashes gold when it's the player's turn
- Turn reminder after 5 s of inaction: gold glow pulse on hand area
+ Android vibration OR two-note Web Audio chime on iOS (vibrate API not
supported by Apple)
- Fix: turn reminder was never triggered after winning a trick — justWon
branch blocked myTurnNow from being set even when currentTurn === mySeat
- Waiting room ☰ menu: Reload and Exit accessible without entering the game
- Prevent duplicate room joins (same socket, same userId, or same name)
Service worker
- Bump to shelem-v2; pre-cache all 55 card SVGs at install time so cards
are available instantly from the very first hand, including offline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-5
@@ -57,6 +57,13 @@
|
||||
<button class="score-btn" data-score="1005">1005</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Visibility</label>
|
||||
<div class="visibility-row">
|
||||
<button class="visibility-btn active" data-public="false">🔒 Private</button>
|
||||
<button class="visibility-btn" data-public="true">🌐 Public</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-create" class="btn-primary">Create Game</button>
|
||||
</div>
|
||||
|
||||
@@ -67,6 +74,14 @@
|
||||
</div>
|
||||
<button id="btn-join" class="btn-primary">Join Game</button>
|
||||
<hr class="divider">
|
||||
<div class="public-rooms-section">
|
||||
<div class="rooms-header">
|
||||
<span class="rooms-label">Public Rooms</span>
|
||||
<button id="btn-refresh-rooms" class="btn-text">↺</button>
|
||||
</div>
|
||||
<div id="public-rooms-list"><p class="hint">Loading…</p></div>
|
||||
</div>
|
||||
<hr class="divider">
|
||||
<div class="field">
|
||||
<label>Watch as spectator</label>
|
||||
<input id="input-spectate-code" type="text" maxlength="8" placeholder="Room code" style="text-transform:uppercase" autocomplete="off">
|
||||
@@ -81,7 +96,16 @@
|
||||
<!-- ══════════════ WAITING ROOM ══════════════ -->
|
||||
<div id="screen-waiting" class="screen">
|
||||
<div class="waiting-box">
|
||||
<button id="btn-leave-waiting" class="btn-leave-screen">← Leave</button>
|
||||
<div class="waiting-top-row">
|
||||
<button id="btn-leave-waiting" class="btn-leave-screen">← Leave</button>
|
||||
<div class="game-menu-wrap" style="margin-left:auto">
|
||||
<button id="btn-waiting-menu" class="btn-leave-screen" title="Options">☰</button>
|
||||
<div id="waiting-menu-dropdown" class="game-menu-dropdown hidden">
|
||||
<button id="btn-waiting-reload" class="game-menu-item">↺ Reload</button>
|
||||
<button id="btn-waiting-exit" class="game-menu-item game-menu-exit">🚪 Exit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Waiting for Players</h2>
|
||||
<div class="room-code-box">
|
||||
<span class="label">Room Code</span>
|
||||
@@ -103,7 +127,10 @@
|
||||
<span class="waiting-opt-label" id="waiting-mode-label"></span>
|
||||
<span class="waiting-opt-label" id="waiting-score-label"></span>
|
||||
</div>
|
||||
<button id="btn-fill-bots" class="btn-fill-bots hidden">🤖 Fill empty seats with bots</button>
|
||||
<div id="waiting-creator-controls" class="waiting-creator-controls hidden">
|
||||
<button id="btn-fill-bots" class="btn-fill-bots">🤖 Fill with bots</button>
|
||||
<button id="btn-start-game" class="btn-start-game">▶ Start Game</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,8 +176,8 @@
|
||||
<span id="top-turn" class="turn-dot hidden">●</span>
|
||||
<span id="top-partner" class="partner-badge">partner</span>
|
||||
<span id="top-score" class="player-score-badge"></span>
|
||||
<span id="top-count" class="card-count-badge"></span>
|
||||
</div>
|
||||
<div id="top-cards" class="opp-cards"></div>
|
||||
</div>
|
||||
|
||||
<!-- Left player (seat 1) -->
|
||||
@@ -159,8 +186,8 @@
|
||||
<span id="left-name">—</span>
|
||||
<span id="left-turn" class="turn-dot hidden">●</span>
|
||||
<span id="left-score" class="player-score-badge"></span>
|
||||
<span id="left-count" class="card-count-badge"></span>
|
||||
</div>
|
||||
<div id="left-cards" class="opp-cards vertical"></div>
|
||||
</div>
|
||||
|
||||
<!-- Center trick area -->
|
||||
@@ -182,8 +209,8 @@
|
||||
<span id="right-name">—</span>
|
||||
<span id="right-turn" class="turn-dot hidden">●</span>
|
||||
<span id="right-score" class="player-score-badge"></span>
|
||||
<span id="right-count" class="card-count-badge"></span>
|
||||
</div>
|
||||
<div id="right-cards" class="opp-cards vertical"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- /table-grid -->
|
||||
|
||||
Reference in New Issue
Block a user