CLAUDE: Fix game recovery to load team display info and add score text outline
Backend: - Add game_metadata to load_game_state() return dict in DatabaseOperations - Populate team display fields (name, color, thumbnail) in _rebuild_state_from_data() so recovered games show team colors/names Frontend: - Add text-outline CSS for score visibility on any background (light logos, gradients) - Handle thumbnail 404 with @error event, show enhanced shadow when no thumbnail - Apply consistent outline across mobile and desktop layouts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d60b7a2d60
commit
64325d7163
@ -389,6 +389,11 @@ class StateManager:
|
|||||||
if current_pitcher and current_catcher:
|
if current_pitcher and current_catcher:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Extract team display info from game_metadata (stored at game creation)
|
||||||
|
game_metadata = game.get("game_metadata") or {}
|
||||||
|
home_meta = game_metadata.get("home_team", {})
|
||||||
|
away_meta = game_metadata.get("away_team", {})
|
||||||
|
|
||||||
state = GameState(
|
state = GameState(
|
||||||
game_id=game["id"],
|
game_id=game["id"],
|
||||||
league_id=game["league_id"],
|
league_id=game["league_id"],
|
||||||
@ -405,6 +410,15 @@ class StateManager:
|
|||||||
current_batter=current_batter_placeholder,
|
current_batter=current_batter_placeholder,
|
||||||
current_pitcher=current_pitcher,
|
current_pitcher=current_pitcher,
|
||||||
current_catcher=current_catcher,
|
current_catcher=current_catcher,
|
||||||
|
# Team display info from metadata
|
||||||
|
home_team_name=home_meta.get("lname"),
|
||||||
|
home_team_abbrev=home_meta.get("abbrev"),
|
||||||
|
home_team_color=home_meta.get("color"),
|
||||||
|
home_team_thumbnail=home_meta.get("thumbnail"),
|
||||||
|
away_team_name=away_meta.get("lname"),
|
||||||
|
away_team_abbrev=away_meta.get("abbrev"),
|
||||||
|
away_team_color=away_meta.get("color"),
|
||||||
|
away_team_thumbnail=away_meta.get("thumbnail"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get last completed play to recover runner state and batter indices
|
# Get last completed play to recover runner state and batter indices
|
||||||
|
|||||||
@ -525,6 +525,7 @@ class DatabaseOperations:
|
|||||||
"current_half": game.current_half,
|
"current_half": game.current_half,
|
||||||
"home_score": game.home_score,
|
"home_score": game.home_score,
|
||||||
"away_score": game.away_score,
|
"away_score": game.away_score,
|
||||||
|
"game_metadata": game.game_metadata, # Team display info
|
||||||
},
|
},
|
||||||
"lineups": [
|
"lineups": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -16,14 +16,21 @@
|
|||||||
<!-- Away Team -->
|
<!-- Away Team -->
|
||||||
<div class="flex-1 text-center relative">
|
<div class="flex-1 text-center relative">
|
||||||
<img
|
<img
|
||||||
v-if="awayTeamThumbnail"
|
v-if="awayTeamThumbnail && !awayThumbnailFailed"
|
||||||
:src="awayTeamThumbnail"
|
:src="awayTeamThumbnail"
|
||||||
alt=""
|
alt=""
|
||||||
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
||||||
|
@error="awayThumbnailFailed = true"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="text-xs font-medium text-blue-100 mb-1">AWAY</div>
|
<div
|
||||||
<div class="text-4xl font-bold tabular-nums">{{ awayScore }}</div>
|
class="text-xs font-medium mb-1 text-outline"
|
||||||
|
:class="showAwayShadow ? 'text-white text-outline-strong' : 'text-blue-100'"
|
||||||
|
>AWAY</div>
|
||||||
|
<div
|
||||||
|
class="text-4xl font-bold tabular-nums text-outline"
|
||||||
|
:class="showAwayShadow ? 'text-outline-strong' : ''"
|
||||||
|
>{{ awayScore }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -87,14 +94,21 @@
|
|||||||
<!-- Home Team -->
|
<!-- Home Team -->
|
||||||
<div class="flex-1 text-center relative">
|
<div class="flex-1 text-center relative">
|
||||||
<img
|
<img
|
||||||
v-if="homeTeamThumbnail"
|
v-if="homeTeamThumbnail && !homeThumbnailFailed"
|
||||||
:src="homeTeamThumbnail"
|
:src="homeTeamThumbnail"
|
||||||
alt=""
|
alt=""
|
||||||
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
||||||
|
@error="homeThumbnailFailed = true"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="text-xs font-medium text-blue-100 mb-1">HOME</div>
|
<div
|
||||||
<div class="text-4xl font-bold tabular-nums">{{ homeScore }}</div>
|
class="text-xs font-medium mb-1 text-outline"
|
||||||
|
:class="showHomeShadow ? 'text-white text-outline-strong' : 'text-blue-100'"
|
||||||
|
>HOME</div>
|
||||||
|
<div
|
||||||
|
class="text-4xl font-bold tabular-nums text-outline"
|
||||||
|
:class="showHomeShadow ? 'text-outline-strong' : ''"
|
||||||
|
>{{ homeScore }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -106,14 +120,21 @@
|
|||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="text-center min-w-[100px] relative">
|
<div class="text-center min-w-[100px] relative">
|
||||||
<img
|
<img
|
||||||
v-if="awayTeamThumbnail"
|
v-if="awayTeamThumbnail && !awayThumbnailFailed"
|
||||||
:src="awayTeamThumbnail"
|
:src="awayTeamThumbnail"
|
||||||
alt=""
|
alt=""
|
||||||
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
||||||
|
@error="awayThumbnailFailed = true"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="text-sm font-medium text-blue-100">AWAY</div>
|
<div
|
||||||
<div class="text-5xl font-bold tabular-nums">{{ awayScore }}</div>
|
class="text-sm font-medium text-outline"
|
||||||
|
:class="showAwayShadow ? 'text-white text-outline-strong' : 'text-blue-100'"
|
||||||
|
>AWAY</div>
|
||||||
|
<div
|
||||||
|
class="text-5xl font-bold tabular-nums text-outline"
|
||||||
|
:class="showAwayShadow ? 'text-outline-strong' : ''"
|
||||||
|
>{{ awayScore }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -179,14 +200,21 @@
|
|||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="text-center min-w-[100px] relative">
|
<div class="text-center min-w-[100px] relative">
|
||||||
<img
|
<img
|
||||||
v-if="homeTeamThumbnail"
|
v-if="homeTeamThumbnail && !homeThumbnailFailed"
|
||||||
:src="homeTeamThumbnail"
|
:src="homeTeamThumbnail"
|
||||||
alt=""
|
alt=""
|
||||||
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
class="absolute inset-0 w-full h-full object-contain opacity-15 pointer-events-none"
|
||||||
|
@error="homeThumbnailFailed = true"
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="text-sm font-medium text-blue-100">HOME</div>
|
<div
|
||||||
<div class="text-5xl font-bold tabular-nums">{{ homeScore }}</div>
|
class="text-sm font-medium text-outline"
|
||||||
|
:class="showHomeShadow ? 'text-white text-outline-strong' : 'text-blue-100'"
|
||||||
|
>HOME</div>
|
||||||
|
<div
|
||||||
|
class="text-5xl font-bold tabular-nums text-outline"
|
||||||
|
:class="showHomeShadow ? 'text-outline-strong' : ''"
|
||||||
|
>{{ homeScore }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +224,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import type { InningHalf } from '~/types/game'
|
import type { InningHalf } from '~/types/game'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -229,6 +257,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
homeTeamThumbnail: undefined
|
homeTeamThumbnail: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Track thumbnail load failures
|
||||||
|
const awayThumbnailFailed = ref(false)
|
||||||
|
const homeThumbnailFailed = ref(false)
|
||||||
|
|
||||||
|
// Show enhanced shadow effect when no thumbnail (missing or failed to load)
|
||||||
|
// Even with thumbnails, we use a subtle outline for readability
|
||||||
|
const showAwayShadow = computed(() => !props.awayTeamThumbnail || awayThumbnailFailed.value)
|
||||||
|
const showHomeShadow = computed(() => !props.homeTeamThumbnail || homeThumbnailFailed.value)
|
||||||
|
|
||||||
// Generate gradient style from team colors
|
// Generate gradient style from team colors
|
||||||
// Uses Option 7: Solid blocks with center blend (away 30% -> dark center 50% -> home 70%)
|
// Uses Option 7: Solid blocks with center blend (away 30% -> dark center 50% -> home 70%)
|
||||||
const gradientStyle = computed(() => {
|
const gradientStyle = computed(() => {
|
||||||
@ -248,6 +285,25 @@ const gradientStyle = computed(() => {
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text outline for readability on any background (light logos, gradients) */
|
||||||
|
.text-outline {
|
||||||
|
text-shadow:
|
||||||
|
-1px -1px 0 rgba(0, 0, 0, 0.5),
|
||||||
|
1px -1px 0 rgba(0, 0, 0, 0.5),
|
||||||
|
-1px 1px 0 rgba(0, 0, 0, 0.5),
|
||||||
|
1px 1px 0 rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced outline when no thumbnail - more prominent shadow */
|
||||||
|
.text-outline-strong {
|
||||||
|
text-shadow:
|
||||||
|
-1px -1px 0 rgba(0, 0, 0, 0.8),
|
||||||
|
1px -1px 0 rgba(0, 0, 0, 0.8),
|
||||||
|
-1px 1px 0 rgba(0, 0, 0, 0.8),
|
||||||
|
1px 1px 0 rgba(0, 0, 0, 0.8),
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
/* Pulse animation for runners */
|
/* Pulse animation for runners */
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% {
|
0%, 100% {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user