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:
Cal Corum 2026-01-17 09:03:59 -06:00
parent d60b7a2d60
commit 64325d7163
3 changed files with 84 additions and 13 deletions

View File

@ -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

View File

@ -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": [
{ {

View File

@ -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% {