Reduce overlap offset from 1.15 to 0.65 so the held card sits at the
DRAW/DISCARD label level rather than up in the opponents area.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Position the held card a full card height above the deck (1.15x offset)
so it sits in the space between opponents and the draw/discard piles.
All three position calculations (app.js x2, card-animations.js) are
synced so draw animations land at the correct held position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap each pile in a .pile-wrapper with a small label above it.
Fix direct child selectors that broke with the new wrapper nesting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase top padding from 5px to 20px so the deck/discard sit lower,
giving the held card more breathing room from the opponents row above.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update getHoldingRect() in card-animations.js and the second held card
positioning path in app.js to use the same reduced overlap offset on
mobile portrait. All three places that compute the held position now
use 0.15 on mobile-portrait vs 0.35 on desktop/landscape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce held card overlap offset from 0.35 to 0.15 on mobile portrait
so it doesn't cover the second row of opponents. Increase bottom
padding on opponents row from 6px to 12px for more breathing room.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The status was set without a type class in renderGame(), overriding
the styled version from updateStatusFromGameState() on every state
update. Now the purple background shows consistently for opponent turns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove margin-top from base rules/leaderboard/matchmaking styles so
desktop and landscape layouts are unaffected. The 50px top margin is
now only applied via the mobile-portrait override.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Override width:100% from .btn base class with width:auto on both
back buttons. Add padding-bottom and border-bottom to leaderboard
header to match rules page styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Position the button absolutely on the left side of the header,
vertically centered with the title. Remove mobile fixed-position
override that placed it in the top bar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents auth bar from overlapping back buttons. Back buttons
align to start instead of stretching full width.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CPU turn chain was awaited inline inside game_lock, blocking the
WebSocket message loop. The end_game message couldn't be processed
until all CPU turns finished. Now check_and_run_cpu_turn launches a
background task and returns immediately, keeping the message loop
responsive. The end_game and leave handlers cancel the task on demand.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert CPU turn chain to a cancellable asyncio.Task tracked on Room,
so ending the game or leaving no longer blocks waiting for CPU sleeps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove game-screen padding and replace solid dark header background
with subtle dark-to-transparent gradient matching mobile treatment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The desktop #game-screen.active had padding:10px that was never
overridden in the mobile portrait styles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces background:none with a dark-to-transparent gradient so the
status message and mute button are visible against the green felt.
Reverts mute button circle in favor of the gradient approach.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The desktop game-header background was still showing on mobile,
creating a visible dark band with padding around the status bar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove left/right/top padding from notification bar so it spans edge
to edge, and increase bottom margin from 4px to 8px for more breathing
room before the opponents row.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace margin:auto on table-center with space-evenly on player-row
so the draw pile and player cards are equally spaced vertically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase horizontal padding on game-table (4px to 10px) and header
(8px to 12px) to prevent edge clipping. Change opponents-row overflow
to visible so dealer chips aren't cut off.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The swap animation defers state updates to pendingGameState, which
bypassed checkForNewPairs entirely. Now pair detection runs when the
deferred state is applied after the swap animation completes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sound was gated behind element check which may fail during swap
animation when card DOM elements are replaced by overlays.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bottom bar is hidden when the hole results modal opens and restored
when dismissed. Also adds mobile-specific compact styles for the
scoresheet: smaller cards, tighter spacing, reduced padding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
firePairCelebration was only doing the visual animation but not playing
the pair sound. The sound was only played during score tallying.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Traefik gets a separate cert for www subdomain and uses
redirectregex middleware to 301 redirect to bare domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove position:fixed from the bottom bar and make it a flex-shrink:0
child of the game screen flex column. This guarantees the game layout
gets exactly the remaining viewport height with no overlap, regardless
of how the browser calculates viewport units.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Set --app-height CSS custom property from window.innerHeight via JS,
which is the only reliable way to get the actual visible viewport on
Chrome Android. Falls back to 100vh if JS hasn't loaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use position:fixed with inset:0 for the game screen instead of
height-based sizing. This bypasses the Chrome Android 100vh bug where
vh includes space behind the dynamic URL bar. Also adds
-webkit-fill-available fallback on body.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 100vh fallback before 100dvh, max-height constraints on every flex
container in the layout chain, and explicit flex-basis 0% to prevent
Chrome from letting flex children grow beyond viewport bounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces fixed 120px width with calc((100% - 20px) / 3) so 3 opponents
always fit per row regardless of viewport width.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Also fix opponent areas shifting between rows on mobile by giving them
a fixed 120px width so name/score changes don't cause reflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduces from 38px to 22px and pulls offset from -10px to -4px so it
no longer overlaps the bottom bar buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add bottom padding to game-table to reserve space for the fixed bottom
bar, and overflow:hidden on player-row so content respects flex bounds.
Also centers draw pile with equal spacing and adds dealer chip clearance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>