15 Commits

Author SHA1 Message Date
adlee-was-taken
7b071afdfb Apply flush header with gradient to desktop/landscape view too
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>
2026-02-22 17:12:15 -05:00
adlee-was-taken
c7fb85d281 Remove desktop 10px padding from game-screen on mobile
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>
2026-02-22 17:11:02 -05:00
adlee-was-taken
118912dd13 Add subtle dark gradient to mobile header for status bar visibility
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>
2026-02-22 17:08:57 -05:00
adlee-was-taken
0e594a5e28 Add dark circle background behind mute button on mobile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:06:08 -05:00
adlee-was-taken
a6ec72d72c Remove dark background from mobile header for flush appearance
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>
2026-02-22 17:05:18 -05:00
adlee-was-taken
e2f353d4ab Make mobile header flush with page edges and add spacing below
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>
2026-02-22 17:01:36 -05:00
adlee-was-taken
e601eb04c9 Add alpha notice banner to lobby screen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:52:22 -05:00
adlee-was-taken
6c771810f7 Distribute space evenly between draw pile and player hand on mobile
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>
2026-02-22 16:50:55 -05:00
adlee-was-taken
dbad7037d1 Fix dealer chip and status bar clipping on mobile edges
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>
2026-02-22 16:50:05 -05:00
adlee-was-taken
21362ba125 Fix pair chime not playing for local player's own swaps
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>
2026-02-22 16:47:43 -05:00
adlee-was-taken
2dcdaf2b49 Remove turns remaining counter from FINAL TURN badge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:46:03 -05:00
adlee-was-taken
1fa13bbe3b Play pair sound before element check and add pair detection debug log
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>
2026-02-22 16:41:17 -05:00
adlee-was-taken
a76fd8da32 Hide bottom bar during scoresheet modal and compact mobile layout
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>
2026-02-22 16:34:16 -05:00
adlee-was-taken
634d101f2c Play pair chime sound for all players including local player
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>
2026-02-22 16:31:00 -05:00
adlee-was-taken
28c9882b17 Add www.golfcards.club cert and redirect to bare domain
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>
2026-02-22 14:48:31 -05:00
4 changed files with 111 additions and 23 deletions

View File

@@ -1573,8 +1573,10 @@ class GolfGame {
this.heldCardFloating.classList.add('hidden');
if (this.pendingGameState) {
const oldState = this.gameState;
this.gameState = this.pendingGameState;
this.pendingGameState = null;
this.checkForNewPairs(oldState, this.gameState);
this.renderGame();
}
}
@@ -1789,6 +1791,10 @@ class GolfGame {
document.body.appendChild(modal);
this.setStatus('Hole complete');
// Hide bottom bar so it doesn't overlay the modal
const bottomBar = document.getElementById('mobile-bottom-bar');
if (bottomBar) bottomBar.classList.add('hidden');
// Bind next button
const nextBtn = document.getElementById('ss-next-btn');
nextBtn.addEventListener('click', () => {
@@ -1918,6 +1924,10 @@ class GolfGame {
this.clearScoresheetCountdown();
const modal = document.getElementById('scoresheet-modal');
if (modal) modal.remove();
// Restore bottom bar
const bottomBar = document.getElementById('mobile-bottom-bar');
if (bottomBar) bottomBar.classList.remove('hidden');
}
// --- V3_02: Dealing Animation ---
@@ -2599,6 +2609,7 @@ class GolfGame {
}
firePairCelebration(playerId, pos1, pos2) {
this.playSound('pair');
const elements = this.getCardElements(playerId, pos1, pos2);
if (elements.length < 2) return;
@@ -3438,15 +3449,6 @@ class GolfGame {
// Toggle game area class for border pulse
this.gameScreen.classList.add('final-turn-active');
// Calculate remaining turns
const remaining = this.countRemainingTurns();
// Update badge content
const remainingEl = this.finalTurnBadge.querySelector('.final-turn-remaining');
if (remainingEl) {
remainingEl.textContent = remaining === 1 ? '1 turn left' : `${remaining} turns left`;
}
// Show badge
this.finalTurnBadge.classList.remove('hidden');

View File

@@ -19,6 +19,8 @@
<h1><img src="golfball-logo.svg" alt="" class="golfball-logo"><span class="golfer-swing">🏌️</span><span class="kicked-ball"></span> <span class="golf-title">Golf</span></h1>
<p class="subtitle">6-Card Golf Card Game <button id="rules-btn" class="btn btn-small btn-rules">Rules</button> <button id="leaderboard-btn" class="btn btn-small leaderboard-btn">Leaderboard</button></p>
<div class="alpha-banner">Alpha &mdash; Things may break. Stats may be wiped.</div>
<!-- Auth prompt for unauthenticated users -->
<div id="auth-prompt" class="auth-prompt">
<p>Log in or sign up to play.</p>
@@ -303,7 +305,6 @@
<div id="final-turn-badge" class="final-turn-badge hidden">
<span class="final-turn-icon"></span>
<span class="final-turn-text">FINAL TURN</span>
<span class="final-turn-remaining"></span>
</div>
</div>
<div class="header-col header-col-right">

View File

@@ -445,7 +445,21 @@ h1 {
.subtitle {
text-align: center;
opacity: 0.8;
margin-bottom: 40px;
margin-bottom: 20px;
}
.alpha-banner {
text-align: center;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.05em;
color: rgba(255, 200, 100, 0.9);
background: rgba(244, 164, 96, 0.1);
border: 1px solid rgba(244, 164, 96, 0.25);
border-radius: 20px;
padding: 5px 16px;
margin: 0 auto 30px;
max-width: 320px;
}
h2 {
@@ -722,8 +736,8 @@ input::placeholder {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
padding: 10px 20px;
background: rgba(0,0,0,0.35);
padding: 6px 12px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.25) 0%, transparent 100%);
font-size: 0.9rem;
width: 100vw;
margin-left: calc(-50vw + 50%);
@@ -1716,7 +1730,7 @@ input::placeholder {
flex-direction: column;
align-items: center;
position: relative;
padding: 10px;
padding: 0;
}
.game-layout {
@@ -4926,6 +4940,7 @@ body.mobile-portrait #game-screen.active {
overflow: hidden;
margin-left: 0;
width: 100%;
padding: 0;
display: flex;
flex-direction: column;
}
@@ -4952,14 +4967,15 @@ body.mobile-portrait .game-header {
display: flex;
flex-direction: row;
align-items: center;
padding: 4px 8px;
padding-top: calc(4px + env(safe-area-inset-top, 0px));
padding: 6px 8px;
padding-top: calc(6px + env(safe-area-inset-top, 0px));
font-size: 0.75rem;
min-height: 32px;
min-height: 0;
width: 100%;
margin-left: 0;
gap: 4px;
margin-bottom: 4px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.25) 0%, transparent 100%);
}
body.mobile-portrait .header-col-left {
@@ -5005,7 +5021,7 @@ body.mobile-portrait #leave-game-btn {
}
body.mobile-portrait .mute-btn {
font-size: 0.85rem;
font-size: 0.95rem;
padding: 2px;
}
@@ -5023,8 +5039,9 @@ body.mobile-portrait .game-table {
justify-content: flex-start;
gap: 0 !important;
flex: 1 1 0%;
overflow: hidden;
padding: 0 4px;
overflow-x: clip;
overflow-y: hidden;
padding: 0 10px;
min-height: 0;
max-height: 100%;
}
@@ -5038,7 +5055,7 @@ body.mobile-portrait .opponents-row {
gap: 4px 10px;
min-height: 0 !important;
padding: 2px 4px 6px;
overflow: hidden;
overflow: visible;
flex-shrink: 0;
}
@@ -5047,7 +5064,7 @@ body.mobile-portrait .player-row {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: space-evenly;
gap: 10px;
width: 100%;
flex: 1 1 0%;
@@ -5113,7 +5130,6 @@ body.mobile-portrait .opponent-showing {
body.mobile-portrait .table-center {
padding: 5px 10px;
border-radius: 8px;
margin: auto 0;
}
body.mobile-portrait .deck-area {
@@ -5367,6 +5383,66 @@ body.mobile-portrait #waiting-screen {
max-height: 100dvh;
}
/* --- Mobile: Compact scoresheet modal --- */
body.mobile-portrait .scoresheet-content {
padding: 14px 16px;
max-height: 90vh;
max-height: var(--app-height, 90vh);
}
body.mobile-portrait .ss-header {
font-size: 0.95rem;
margin-bottom: 10px;
}
body.mobile-portrait .ss-players {
gap: 8px;
}
body.mobile-portrait .ss-player-row {
padding: 8px 10px;
}
body.mobile-portrait .ss-player-header {
margin-bottom: 4px;
}
body.mobile-portrait .ss-player-name {
font-size: 0.8rem;
}
body.mobile-portrait .ss-mini-card {
width: 30px;
height: 22px;
font-size: 0.6rem;
}
body.mobile-portrait .ss-columns {
gap: 6px;
margin-bottom: 4px;
}
body.mobile-portrait .ss-column {
gap: 2px;
padding: 3px 4px;
}
body.mobile-portrait .ss-col-score {
font-size: 0.6rem;
}
body.mobile-portrait .ss-scores {
font-size: 0.7rem;
gap: 12px;
margin-top: 2px;
}
body.mobile-portrait .ss-next-btn {
margin-top: 10px;
padding: 8px;
font-size: 0.85rem;
}
/* --- Mobile: Very short screens (e.g. iPhone SE) --- */
@media (max-height: 600px) {
body.mobile-portrait .opponents-row {

View File

@@ -61,6 +61,15 @@ services:
- "traefik.http.routers.golf.entrypoints=websecure"
- "traefik.http.routers.golf.tls=true"
- "traefik.http.routers.golf.tls.certresolver=letsencrypt"
# www -> bare domain redirect
- "traefik.http.routers.golf-www.rule=Host(`www.${DOMAIN:-golf.example.com}`)"
- "traefik.http.routers.golf-www.entrypoints=websecure"
- "traefik.http.routers.golf-www.tls=true"
- "traefik.http.routers.golf-www.tls.certresolver=letsencrypt"
- "traefik.http.routers.golf-www.middlewares=www-redirect"
- "traefik.http.middlewares.www-redirect.redirectregex.regex=^https://www\\.(.+)"
- "traefik.http.middlewares.www-redirect.redirectregex.replacement=https://$${1}"
- "traefik.http.middlewares.www-redirect.redirectregex.permanent=true"
- "traefik.http.services.golf.loadbalancer.server.port=8000"
# WebSocket sticky sessions
- "traefik.http.services.golf.loadbalancer.sticky.cookie=true"