golfgame/docs/v3/V3_17_MOBILE_PORTRAIT_LAYOUT.md
adlee-was-taken 3ca52eb7d1 Bump version to 3.1.6, update docs
Update version across pyproject.toml, FastAPI app, HTML footers,
and V3.17 doc. Mark kicked-ball bug as resolved in docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:59:54 -05:00

5.3 KiB

V3.17: Mobile Portrait Layout

Version: 3.1.6 Commits: 4fcdf13, fb3bd53

Overview

Full mobile portrait layout for phones, triggered by JS matchMedia on narrow portrait screens (max-width: 500px, orientation: portrait). The desktop layout is completely untouched — all mobile rules are scoped under body.mobile-portrait.

Key Features

Responsive Game Layout

  • Viewport fills 100dvh with no scroll; overscroll-behavior: contain prevents pull-to-refresh
  • Game screen uses flexbox column: compact header → opponents row → player row → bottom bar
  • Safe-area insets respected for notched devices (env(safe-area-inset-top/bottom))

Compact Header

  • Single-row header with reduced font sizes (0.75rem) and tight gaps
  • Non-essential items hidden on mobile: username display, logout button, active rules bar
  • Status message, round info, final turn badge, and leave button all use white-space: nowrap with ellipsis overflow

Opponent Cards

  • Flat horizontal strip (no arch rotation) with horizontal scroll for 4+ opponents
  • Cards scaled to 32x45px with 0.6rem font (26x36px on short screens)
  • Dealer chip scaled from 38px to 20px diameter to fit compact opponent areas
  • Showing score badge sized proportionally

Deck/Discard Area

  • Deck and discard cards match player card size (72x101px) for visual consistency
  • Held card floating matches player card size with proportional font scaling

Player Cards

  • Fixed 72x101px cards with 1.5rem font in 3-column grid
  • 60x84px with 1.3rem font on short screens (max-height: 600px)
  • Font size set inline by card-manager.js proportional to card width (0.35x ratio on mobile)

Side Panels as Bottom Drawers

  • Standings and scoreboard panels slide up as bottom drawers from a mobile bottom bar
  • Drawer backdrop overlay with tap-to-dismiss
  • Drag handle visual indicator on each drawer
  • Drawers auto-close on screen change or layout change back to desktop

Short Screen Fallback

  • @media (max-height: 600px) reduces all card sizes, gaps, and padding
  • Opponent cards: 26x36px, deck/discard: 60x84px, player cards: 60x84px

Animation Fixes

Deal Animation Guard

  • renderGame() returns early when dealAnimationInProgress is true
  • Prevents WebSocket state updates from destroying card slot DOM elements mid-deal animation
  • Cards were piling up at (0,0) because getCardSlotRect() read stale/null positions after innerHTML = ''

Animation Overlay Card Sizing

  • Root cause: Base .card CSS (width: clamp(65px, 5.5vw, 100px)) was leaking into animation overlay elements (.draw-anim-front.card), overriding the intended width: 100% inherited from the overlay container
  • Effect: Opponent flip overlays appeared at 65px instead of 32px (too big); deck/discard draw overlays appeared at 65px instead of 72px (too small)
  • Fix: Added !important to .draw-anim-front/.draw-anim-back width and height rules to ensure animation overlays always match their parent container's inline dimensions from JavaScript

Opponent Swap Held Card Sizing

  • fireSwapAnimation() now passes a heldRect sized to match the opponent card (32px) positioned at the holding location, instead of defaulting to deck dimensions (72px)
  • The traveling held card no longer appears oversized relative to opponent cards during the swap arc

Font Size Consistency

  • cardFontSize() helper in CardAnimations uses 0.35x width ratio on mobile (vs 0.5x desktop)
  • Applied consistently across all animation paths: createAnimCard, createCardFromData, and arc swap font transitions
  • Held card floating gets inline font-size scaled to card width on mobile

CSS Architecture

All mobile rules use the body.mobile-portrait scope:

/* Applied by JS matchMedia, not CSS media query */
body.mobile-portrait .selector { ... }

/* Short screen fallback uses both */
@media (max-height: 600px) {
    body.mobile-portrait .selector { ... }
}

Card sizing uses !important to override base .card clamp values:

body.mobile-portrait .opponent-area .card {
    width: 32px !important;
    height: 45px !important;
}

Animation overlays use !important to override base .card leaking:

.draw-anim-front,
.draw-anim-back {
    width: 100% !important;
    height: 100% !important;
}

Files Modified

File Changes
client/style.css ~470 lines of mobile portrait CSS added at end of file
client/app.js Mobile detection, drawer management, renderGame() guard, swap heldRect sizing, held card font scaling
client/card-animations.js cardFontSize() helper, consistent font scaling across all animation paths
client/card-manager.js Inline font-size on mobile for updateCardElement()
client/index.html Mobile bottom bar, drawer backdrop, viewport-fit=cover

Testing

  • Desktop: No visual changes — all rules scoped under body.mobile-portrait
  • Mobile portrait: Verify game fits 100dvh, no scroll, cards properly sized
  • Deal animation: Cards fly to correct grid positions (not piling up)
  • Draw/discard: Animation overlay matches source card size
  • Opponent swap: Flip and arc animations use opponent card dimensions
  • Short screens (iPhone SE): All elements fit with reduced sizes
  • Orientation change: Layout switches cleanly between mobile and desktop