Fix mobile portrait layout: lobby overlap, deal animation, card font sizes

- Add renderGame() guard during deal animation to prevent DOM destruction
  mid-animation causing cards to pile up at wrong positions
- Push lobby content below fixed auth-bar (padding 15px -> 50px top)
- Scale player card font-size to 1.5rem/1.3rem for readable text on mobile
- Add full mobile portrait layout: bottom drawers, compact header, responsive
  card grid sizing, safe-area insets, and mobile detection via matchMedia
- Add cardFontSize() helper for consistent proportional font scaling
- Add mobile bottom bar with drawer toggles for standings/scores

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-02-21 22:52:44 -05:00
parent 6673e63241
commit 4fcdf13f66
5 changed files with 595 additions and 21 deletions

View File

@@ -66,10 +66,14 @@ class GolfGame {
this.discardHistory = [];
this.maxDiscardHistory = 5;
// Mobile detection
this.isMobile = false;
this.initElements();
this.initAudio();
this.initCardTooltips();
this.bindEvents();
this.initMobileDetection();
this.checkUrlParams();
}
@@ -85,6 +89,51 @@ class GolfGame {
}
}
initMobileDetection() {
const mql = window.matchMedia('(max-width: 500px) and (orientation: portrait)');
const update = (e) => {
this.isMobile = e.matches;
document.body.classList.toggle('mobile-portrait', e.matches);
// Close any open drawers on layout change
if (!e.matches) {
this.closeDrawers();
}
};
mql.addEventListener('change', update);
update(mql);
// Bottom bar drawer toggles
const bottomBar = document.getElementById('mobile-bottom-bar');
const backdrop = document.getElementById('drawer-backdrop');
if (bottomBar) {
bottomBar.addEventListener('click', (e) => {
const btn = e.target.closest('.mobile-bar-btn');
if (!btn) return;
const drawerId = btn.dataset.drawer;
const panel = document.getElementById(drawerId);
if (!panel) return;
const isOpen = panel.classList.contains('drawer-open');
this.closeDrawers();
if (!isOpen) {
panel.classList.add('drawer-open');
btn.classList.add('active');
if (backdrop) backdrop.classList.add('visible');
}
});
}
if (backdrop) {
backdrop.addEventListener('click', () => this.closeDrawers());
}
}
closeDrawers() {
document.querySelectorAll('.side-panel.drawer-open').forEach(p => p.classList.remove('drawer-open'));
document.querySelectorAll('.mobile-bar-btn.active').forEach(b => b.classList.remove('active'));
const backdrop = document.getElementById('drawer-backdrop');
if (backdrop) backdrop.classList.remove('visible');
}
initAudio() {
// Initialize audio context on first user interaction
const initCtx = () => {
@@ -1876,20 +1925,33 @@ class GolfGame {
this.dealAnimationInProgress = true;
if (window.cardAnimations) {
window.cardAnimations.animateDealing(
this.gameState,
(playerId, cardIdx) => this.getCardSlotRect(playerId, cardIdx),
() => {
// Deal complete - allow flip prompts
this.dealAnimationInProgress = false;
// Show real cards
this.playerCards.style.visibility = 'visible';
this.opponentsRow.style.visibility = 'visible';
this.renderGame();
// Stagger opponent initial flips right after dealing
this.animateOpponentInitialFlips();
}
);
// Use double-rAF to ensure layout is fully computed after renderGame().
// First rAF: browser computes styles. Second rAF: layout is painted.
// This is critical on mobile where CSS !important rules need to apply
// before getBoundingClientRect() returns correct card slot positions.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
// Verify rects are valid before starting animation
const testRect = this.getCardSlotRect(this.playerId, 0);
if (this.isMobile) {
console.log('[DEAL] Starting deal animation, test rect:', testRect);
}
window.cardAnimations.animateDealing(
this.gameState,
(playerId, cardIdx) => this.getCardSlotRect(playerId, cardIdx),
() => {
// Deal complete - allow flip prompts
this.dealAnimationInProgress = false;
// Show real cards
this.playerCards.style.visibility = 'visible';
this.opponentsRow.style.visibility = 'visible';
this.renderGame();
// Stagger opponent initial flips right after dealing
this.animateOpponentInitialFlips();
}
);
});
});
} else {
// Fallback
this.dealAnimationInProgress = false;
@@ -1950,14 +2012,22 @@ class GolfGame {
getCardSlotRect(playerId, cardIdx) {
if (playerId === this.playerId) {
const cards = this.playerCards.querySelectorAll('.card');
return cards[cardIdx]?.getBoundingClientRect() || null;
const rect = cards[cardIdx]?.getBoundingClientRect() || null;
if (this.isMobile && rect) {
console.log(`[DEAL-DEBUG] Player card[${cardIdx}] rect:`, {l: rect.left, t: rect.top, w: rect.width, h: rect.height});
}
return rect;
} else {
const area = this.opponentsRow.querySelector(
`.opponent-area[data-player-id="${playerId}"]`
);
if (area) {
const cards = area.querySelectorAll('.card');
return cards[cardIdx]?.getBoundingClientRect() || null;
const rect = cards[cardIdx]?.getBoundingClientRect() || null;
if (this.isMobile && rect) {
console.log(`[DEAL-DEBUG] Opponent ${playerId} card[${cardIdx}] rect:`, {l: rect.left, t: rect.top, w: rect.width, h: rect.height});
}
return rect;
}
}
return null;
@@ -2932,6 +3002,11 @@ class GolfGame {
}
screen.classList.add('active');
// Close mobile drawers on screen change
if (this.isMobile) {
this.closeDrawers();
}
// Handle auth bar visibility - hide global bar during game, show in-game controls instead
const isGameScreen = screen === this.gameScreen;
const user = this.auth?.user;
@@ -3444,6 +3519,10 @@ class GolfGame {
this.heldCardFloating.style.top = `${cardTop}px`;
this.heldCardFloating.style.width = `${cardWidth}px`;
this.heldCardFloating.style.height = `${cardHeight}px`;
// Scale font to card width (matches cardAnimations.cardFontSize ratio)
if (this.isMobile) {
this.heldCardFloating.style.fontSize = `${cardWidth * 0.35}px`;
}
// Position discard button attached to right side of held card
const buttonLeft = cardLeft + cardWidth; // Right edge of card (no gap)
@@ -3503,6 +3582,9 @@ class GolfGame {
this.heldCardFloating.style.top = `${cardTop}px`;
this.heldCardFloating.style.width = `${cardWidth}px`;
this.heldCardFloating.style.height = `${cardHeight}px`;
if (this.isMobile) {
this.heldCardFloating.style.fontSize = `${cardWidth * 0.35}px`;
}
this.heldCardFloatingContent.innerHTML = '';
this.heldCardFloating.classList.remove('hidden');
@@ -3648,6 +3730,7 @@ class GolfGame {
renderGame() {
if (!this.gameState) return;
if (this.dealAnimationInProgress) return;
// Update CPU considering visual state
this.updateCpuConsideringState();