Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca52eb7d1 | ||
|
|
3c63af91f2 | ||
|
|
5fcf8bab60 | ||
|
|
8bc8595b39 | ||
|
|
7c58543ec8 | ||
|
|
4b00094140 | ||
|
|
65d6598a51 | ||
|
|
baa471307e | ||
|
|
26778e4b02 | ||
|
|
cce2d661a2 | ||
|
|
1b748470a0 | ||
|
|
d32ae83ce2 | ||
|
|
e542cadedf | ||
|
|
cd2d7535e3 | ||
|
|
4dff1da875 | ||
|
|
8f21a40a6a | ||
|
|
0ae999aca6 | ||
|
|
a87cd7f4b0 | ||
|
|
eb072dbfb4 | ||
|
|
4c16147ace | ||
|
|
cac1e26bac | ||
|
|
31dcb70fc8 | ||
|
|
15339d390f | ||
|
|
c523b144f5 | ||
|
|
0f3ae992f9 | ||
|
|
ce6b276c11 | ||
|
|
231e666407 | ||
|
|
7842de3a96 | ||
|
|
aab41c5413 | ||
|
|
625320992e | ||
|
|
61713f28c8 | ||
|
|
0eac6d443c |
@@ -59,9 +59,9 @@
|
|||||||
<!-- Outer edge highlight -->
|
<!-- Outer edge highlight -->
|
||||||
<circle cx="50" cy="44" r="46" fill="none" stroke="#ffffff" stroke-width="0.5" opacity="0.5"/>
|
<circle cx="50" cy="44" r="46" fill="none" stroke="#ffffff" stroke-width="0.5" opacity="0.5"/>
|
||||||
|
|
||||||
<!-- Card suits - single row, larger -->
|
<!-- Card suits - 2x2 grid -->
|
||||||
<text x="22" y="51" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#1a1a1a" text-anchor="middle">♣</text>
|
<text x="36" y="40" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="#1a1a1a" text-anchor="middle">♣</text>
|
||||||
<text x="41" y="51" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#cc0000" text-anchor="middle">♦</text>
|
<text x="64" y="40" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="#cc0000" text-anchor="middle">♦</text>
|
||||||
<text x="59" y="51" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#1a1a1a" text-anchor="middle">♠</text>
|
<text x="36" y="64" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="#cc0000" text-anchor="middle">♥</text>
|
||||||
<text x="77" y="51" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#cc0000" text-anchor="middle">♥</text>
|
<text x="64" y="64" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="#1a1a1a" text-anchor="middle">♠</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<!-- Lobby Screen -->
|
<!-- Lobby Screen -->
|
||||||
<div id="lobby-screen" class="screen active">
|
<div id="lobby-screen" class="screen active">
|
||||||
<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>
|
<h1><span class="logo-row"><img src="golfball-logo.svg" alt="" class="golfball-logo"><span class="golfer-container"><span class="golfer-swing">🏌️</span><span class="kicked-ball">⚪</span></span></span> <span class="golf-title">GolfCards<span class="golf-title-tld">.club</span></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>
|
<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">Beta Testing - Bear with us while, stuff.</div>
|
<div class="alpha-banner">Beta Testing - Bear with us while, stuff.</div>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
<p id="lobby-error" class="error"></p>
|
<p id="lobby-error" class="error"></p>
|
||||||
|
|
||||||
<footer class="app-footer">v3.1.5 © Aaron D. Lee</footer>
|
<footer class="app-footer">v3.1.6 © Aaron D. Lee</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Matchmaking Screen -->
|
<!-- Matchmaking Screen -->
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
<p id="waiting-message" class="info">Waiting for host to start the game...</p>
|
<p id="waiting-message" class="info">Waiting for host to start the game...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="app-footer">v3.1.5 © Aaron D. Lee</footer>
|
<footer class="app-footer">v3.1.6 © Aaron D. Lee</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Game Screen -->
|
<!-- Game Screen -->
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ body {
|
|||||||
|
|
||||||
/* Lobby Screen */
|
/* Lobby Screen */
|
||||||
#lobby-screen {
|
#lobby-screen {
|
||||||
max-width: 400px;
|
max-width: 550px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -59,7 +59,8 @@ body {
|
|||||||
|
|
||||||
/* Golf title - golf ball with dimples and shine */
|
/* Golf title - golf ball with dimples and shine */
|
||||||
.golf-title {
|
.golf-title {
|
||||||
font-size: 1.3em;
|
display: block;
|
||||||
|
font-size: 1.05em;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
/* Shiny gradient like a golf ball surface */
|
/* Shiny gradient like a golf ball surface */
|
||||||
@@ -86,15 +87,72 @@ body {
|
|||||||
filter: drop-shadow(1px 1px 1px rgba(0,0,0,0.15));
|
filter: drop-shadow(1px 1px 1px rgba(0,0,0,0.15));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.golf-title-tld {
|
||||||
|
font-size: 0.45em;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
/* Golf ball logo with card suits */
|
/* Golf ball logo with card suits */
|
||||||
.golfball-logo {
|
.golfball-logo {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 18px;
|
margin-right: 8px;
|
||||||
filter: drop-shadow(1px 2px 2px rgba(0,0,0,0.25));
|
filter: drop-shadow(1px 2px 2px rgba(0,0,0,0.25));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#lobby-screen h1 {
|
||||||
|
display: inline-grid;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
justify-items: start;
|
||||||
|
text-align: left;
|
||||||
|
row-gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-row {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Golfer + ball container */
|
||||||
|
.golfer-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lobby-game-controls,
|
||||||
|
#auth-prompt {
|
||||||
|
max-width: 400px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
#lobby-screen h1 {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
text-indent: -18px;
|
||||||
|
}
|
||||||
|
.logo-row {
|
||||||
|
display: inline;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.golf-title {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.golfball-logo {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
.golfer-swing {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.golfer-container {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Golfer swing animation */
|
/* Golfer swing animation */
|
||||||
.golfer-swing {
|
.golfer-swing {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -133,44 +191,46 @@ body {
|
|||||||
.kicked-ball {
|
.kicked-ball {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.2em;
|
font-size: 0.2em;
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
right: -8px;
|
||||||
|
bottom: 30%;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation: ball-kicked 0.7s linear forwards;
|
animation: ball-kicked 0.7s linear forwards;
|
||||||
animation-delay: 0.72s;
|
animation-delay: 0.72s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trajectory: y = 0.0124x² - 1.42x (parabola with peak at x=57) */
|
/* Trajectory: parabolic arc from golfer's front foot, up and to the right */
|
||||||
@keyframes ball-kicked {
|
@keyframes ball-kicked {
|
||||||
0% {
|
0% {
|
||||||
transform: translate(-12px, 8px) scale(1);
|
transform: translate(0, 0) scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
15% {
|
15% {
|
||||||
transform: translate(8px, -16px) scale(1);
|
transform: translate(20px, -24px) scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
30% {
|
30% {
|
||||||
transform: translate(28px, -31px) scale(1);
|
transform: translate(40px, -39px) scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
45% {
|
45% {
|
||||||
transform: translate(48px, -38px) scale(0.95);
|
transform: translate(60px, -46px) scale(0.95);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
55% {
|
55% {
|
||||||
transform: translate(63px, -38px) scale(0.9);
|
transform: translate(75px, -46px) scale(0.9);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
70% {
|
70% {
|
||||||
transform: translate(83px, -27px) scale(0.85);
|
transform: translate(95px, -35px) scale(0.85);
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
85% {
|
85% {
|
||||||
transform: translate(103px, -6px) scale(0.75);
|
transform: translate(115px, -14px) scale(0.75);
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translate(118px, 25px) scale(0.65);
|
transform: translate(130px, 17px) scale(0.65);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
77
docs/BUG-kicked-ball-position.md
Normal file
77
docs/BUG-kicked-ball-position.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# BUG: Kicked ball animation starts from golfer's back foot
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The `⚪` kicked ball animation (`.kicked-ball`) appears to launch from the golfer's **back foot** (left side) instead of the **front foot** (right side). The golfer faces right in both landscape (two-row) and mobile (single-line) views due to `scaleX(-1)`.
|
||||||
|
|
||||||
|
## What we want
|
||||||
|
|
||||||
|
The ball should appear at the golfer's front foot (right side) and arc up and to the right — matching the "good" landscape behavior seen at wide desktop widths (~1100px+).
|
||||||
|
|
||||||
|
## Good reference
|
||||||
|
|
||||||
|
- Video: `good.mp4` (landscape wide view)
|
||||||
|
- Extracted frames: `/tmp/golf-frames-good/`
|
||||||
|
- Frame 025: Ball clearly appears to the RIGHT of the golfer, arcing up-right
|
||||||
|
|
||||||
|
## Bad behavior
|
||||||
|
|
||||||
|
- Videos: `Screencast_20260224_005555.mp4`, `Screencast_20260224_013326.mp4`
|
||||||
|
- The ball appears to the LEFT of the golfer (between the golf ball logo and golfer emoji)
|
||||||
|
- Happens at the user's phone viewport width (two-row layout, inline-grid)
|
||||||
|
|
||||||
|
## Root cause analysis
|
||||||
|
|
||||||
|
### The scaleX(-1) offset problem
|
||||||
|
|
||||||
|
The golfer emoji (`.golfer-swing`) has `transform: scaleX(-1)` which flips it visually. This means:
|
||||||
|
- The golfer's **layout box** occupies the same inline flow position
|
||||||
|
- But the **visual** left/right is flipped — the front foot (visually on the right) is at the LEFT edge of the layout box
|
||||||
|
- The `.kicked-ball` span comes right after `.golfer-swing` in inline flow, so its natural position is at the **right edge** of the golfer's layout box
|
||||||
|
- But due to `scaleX(-1)`, the right edge of the layout box is the golfer's **visual back** (left side)
|
||||||
|
- So `translate(0, 0)` places the ball at the golfer's back, not front
|
||||||
|
|
||||||
|
### CSS translate values tested
|
||||||
|
|
||||||
|
| Start X | Result |
|
||||||
|
|---------|--------|
|
||||||
|
| `-30px` (original) | Ball appears way behind golfer (further left) |
|
||||||
|
| `+20px` | Ball still appears to LEFT of golfer, but slightly closer |
|
||||||
|
| `+80px` | Not confirmed (staging 404 during test) |
|
||||||
|
|
||||||
|
### Key finding: The kicked-ball's natural position needs ~60-80px positive X offset to reach the golfer's visual front foot
|
||||||
|
|
||||||
|
The golfer emoji is roughly 30-40px wide at this viewport. Since `scaleX(-1)` flips the visual, the ball needs to translate **past the entire emoji width** to reach the visual front.
|
||||||
|
|
||||||
|
### Media query issues encountered
|
||||||
|
|
||||||
|
1. First attempt: Added `ball-kicked-mobile` keyframes with `@media (max-width: 500px)` override
|
||||||
|
2. **CSS source order bug**: The mobile override at line 144 was being overridden by the base `.kicked-ball` rule at line 216 (later = higher priority at equal specificity)
|
||||||
|
3. Moved override after base rule — still didn't work
|
||||||
|
4. Added `!important` — still didn't work
|
||||||
|
5. Raised breakpoint from 500px to 768px, then 1200px — still no visible change
|
||||||
|
6. **Breakthrough**: Added `outline: 3px solid red; background: yellow` debug styles to base `.kicked-ball` — these DID appear, confirming CSS was loading
|
||||||
|
7. Changed base `ball-kicked` keyframes from `-30px` to `+20px` — ball DID move, confirming the base keyframes are what's being used
|
||||||
|
8. The mobile override keyframes may never have been applied (unclear if `ball-kicked-mobile` was actually used)
|
||||||
|
|
||||||
|
### What the Chrome extension Claude analysis said
|
||||||
|
|
||||||
|
> "The breakpoint is 500px, but the viewport is above 500px. At 700px+, ball-kicked-mobile never kicks in — it still uses the desktop ball-kicked animation. But the layout at this width has already shifted to a more centered layout which changes where .kicked-ball is positioned relative to the golfer."
|
||||||
|
|
||||||
|
## Suggested fix approach
|
||||||
|
|
||||||
|
1. **Don't use separate mobile keyframes** — just fix the base `ball-kicked` to work at all viewport widths
|
||||||
|
2. The starting X needs to be **much larger positive** (60-80px) to account for `scaleX(-1)` placing the natural position at the golfer's visual back
|
||||||
|
3. Alternatively, restructure the HTML: move `.kicked-ball` BEFORE `.golfer-swing` in the DOM, so its natural inline position is at the golfer's visual front (since scaleX(-1) flips left/right)
|
||||||
|
4. Or use `position: absolute` on `.kicked-ball` and position it relative to the golfer container explicitly
|
||||||
|
|
||||||
|
## Files involved
|
||||||
|
|
||||||
|
- `client/style.css` — `.kicked-ball`, `@keyframes ball-kicked`, `.golfer-swing`
|
||||||
|
- `client/index.html` — line 19: `<span class="golfer-swing">🏌️</span><span class="kicked-ball">⚪</span>`
|
||||||
|
|
||||||
|
## Resolution (v3.1.6)
|
||||||
|
|
||||||
|
**Fixed** by wrapping `.golfer-swing` + `.kicked-ball` in a `.golfer-container` span with `position: relative`, and changing `.kicked-ball` from `position: relative` to `position: absolute; right: -8px; bottom: 30%`. This anchors the ball to the golfer's front foot regardless of viewport width or inline flow layout.
|
||||||
|
|
||||||
|
Also fixed a **CSS source order bug** where the base `.golfer-container` rule was defined after the `@media (max-width: 500px)` override, clobbering the mobile margin-left value.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# V3.17: Mobile Portrait Layout
|
# V3.17: Mobile Portrait Layout
|
||||||
|
|
||||||
**Version:** 3.1.1
|
**Version:** 3.1.6
|
||||||
**Commits:** `4fcdf13`, `fb3bd53`
|
**Commits:** `4fcdf13`, `fb3bd53`
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "golfgame"
|
name = "golfgame"
|
||||||
version = "3.1.1"
|
version = "3.1.6"
|
||||||
description = "6-Card Golf card game with AI opponents"
|
description = "6-Card Golf card game with AI opponents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ async def _close_all_websockets():
|
|||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Golf Card Game",
|
title="Golf Card Game",
|
||||||
debug=config.DEBUG,
|
debug=config.DEBUG,
|
||||||
version="3.1.1",
|
version="3.1.6",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user