From b7b21d83787a50d4e3263a45e99124f460a38cd4 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 21 Feb 2026 23:14:06 -0500 Subject: [PATCH] Bump version to 3.1.1, add mobile portrait layout documentation - Update version from 2.0.1 to 3.1.1 in pyproject.toml and server/main.py - Add V3_17_MOBILE_PORTRAIT_LAYOUT.md documenting all mobile improvements: responsive layout, animation sizing fixes, compact header, bottom drawers - Add V3_17 entry to V3 master plan Co-Authored-By: Claude Opus 4.6 --- docs/v3/V3_00_MASTER_PLAN.md | 1 + docs/v3/V3_17_MOBILE_PORTRAIT_LAYOUT.md | 117 ++++++++++++++++++++++++ pyproject.toml | 2 +- server/main.py | 2 +- 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 docs/v3/V3_17_MOBILE_PORTRAIT_LAYOUT.md diff --git a/docs/v3/V3_00_MASTER_PLAN.md b/docs/v3/V3_00_MASTER_PLAN.md index a46a063..da479f4 100644 --- a/docs/v3/V3_00_MASTER_PLAN.md +++ b/docs/v3/V3_00_MASTER_PLAN.md @@ -30,6 +30,7 @@ This plan is split into independent vertical slices ordered by priority and impa | `V3_14_ACTIVE_RULES_CONTEXT.md` | Contextual rule highlighting | Low | Low | None | | `V3_15_DISCARD_PILE_HISTORY.md` | Show recent discards fanned | Low | Medium | None | | `V3_16_REALISTIC_CARD_SOUNDS.md` | Improved audio feedback | Nice | Medium | None | +| `V3_17_MOBILE_PORTRAIT_LAYOUT.md` | Full mobile portrait layout + animation fixes | High | High | 02, 11 | --- diff --git a/docs/v3/V3_17_MOBILE_PORTRAIT_LAYOUT.md b/docs/v3/V3_17_MOBILE_PORTRAIT_LAYOUT.md new file mode 100644 index 0000000..ec0320d --- /dev/null +++ b/docs/v3/V3_17_MOBILE_PORTRAIT_LAYOUT.md @@ -0,0 +1,117 @@ +# V3.17: Mobile Portrait Layout + +**Version:** 3.1.1 +**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: + +```css +/* 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: +```css +body.mobile-portrait .opponent-area .card { + width: 32px !important; + height: 45px !important; +} +``` + +Animation overlays use `!important` to override base `.card` leaking: +```css +.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 diff --git a/pyproject.toml b/pyproject.toml index 7b7394f..53d2378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "golfgame" -version = "2.0.1" +version = "3.1.1" description = "6-Card Golf card game with AI opponents" readme = "README.md" requires-python = ">=3.11" diff --git a/server/main.py b/server/main.py index 2fd0b78..cf00f9e 100644 --- a/server/main.py +++ b/server/main.py @@ -325,7 +325,7 @@ async def _close_all_websockets(): app = FastAPI( title="Golf Card Game", debug=config.DEBUG, - version="2.0.1", + version="3.1.1", lifespan=lifespan, )