strat-gameplay-webapp/frontend-sba/components/Game/RunnerCard.vue
Cal Corum 46caf9cd81 CLAUDE: Update color scheme - red for runners, blue for catcher
Runner highlights and cards:
- Pills: red-500 ring, red-50 background when selected
- Full cards: red gradient (red-900 to red-950), red-600 border
- Pulse glow: red animation (rgba(239, 68, 68))
- Hardcoded red color (#ef4444) for runner pill borders

Catcher highlights and cards:
- Pill: blue-500 ring, blue-50 background when selected
- Full card: blue gradient (blue-900 to blue-950), blue-600 border
- Pulse glow: blue animation (rgba(59, 130, 246))

Updated tests to expect new colors

All 15 RunnersOnBase tests passing
All 16 RunnerCard tests passing
2026-02-07 23:50:24 -06:00

109 lines
3.0 KiB
Vue

<template>
<div
:class="[
'runner-pill',
runner ? 'occupied' : 'empty',
isSelected ? 'selected' : ''
]"
@click="handleClick"
>
<template v-if="runner">
<!-- Occupied base -->
<div class="w-8 h-8 rounded-full flex-shrink-0 overflow-hidden border-2" :style="{ borderColor: teamColor }">
<img
v-if="runnerPlayer?.headshot || runnerPlayer?.image"
:src="runnerPlayer.headshot || runnerPlayer.image"
:alt="runnerName"
class="w-full h-full object-cover"
>
<div v-else class="w-full h-full bg-gray-300 flex items-center justify-center text-gray-600 font-bold text-xs">
{{ base }}
</div>
</div>
<div class="ml-2 flex-1 min-w-0">
<div class="text-xs font-bold text-gray-900 truncate">{{ runnerName }}</div>
<div class="text-[10px] text-gray-500">{{ base }}</div>
</div>
</template>
<template v-else>
<!-- Empty base -->
<div class="w-8 h-8 rounded-full bg-gray-200 border-2 border-dashed border-gray-400 flex items-center justify-center flex-shrink-0">
<span class="text-gray-400 text-[10px] font-bold">{{ base }}</span>
</div>
<div class="ml-2 text-xs text-gray-400">Empty</div>
</template>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { LineupPlayerState } from '~/types/game'
import { useGameStore } from '~/store/game'
interface Props {
base: '1B' | '2B' | '3B'
runner: LineupPlayerState | null
isSelected: boolean
teamColor: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
click: []
}>()
const gameStore = useGameStore()
// Resolve player data from lineup
const runnerPlayer = computed(() => {
if (!props.runner) return null
const lineupEntry = gameStore.findPlayerInLineup(props.runner.lineup_id)
return lineupEntry?.player ?? null
})
const runnerName = computed(() => {
if (!runnerPlayer.value) return 'Unknown Runner'
return runnerPlayer.value.name
})
const runnerNumber = computed(() => {
// Try to extract jersey number from player data if available
// For now, default to a placeholder based on lineup_id
return props.runner?.lineup_id?.toString().padStart(2, '0') ?? '00'
})
const getRunnerInitials = computed(() => {
if (!runnerPlayer.value) return '?'
const parts = runnerPlayer.value.name.split(' ')
if (parts.length >= 2) {
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
}
return runnerPlayer.value.name.substring(0, 2).toUpperCase()
})
function handleClick() {
if (props.runner) {
emit('click')
}
}
</script>
<style scoped>
.runner-pill {
@apply flex items-center p-2 rounded-lg bg-white border border-gray-200
shadow-sm transition-all duration-200 cursor-default;
}
.runner-pill.occupied {
@apply cursor-pointer hover:bg-red-50;
}
.runner-pill.selected {
@apply ring-2 ring-red-500 bg-red-50 shadow-md;
}
.runner-pill.empty {
@apply bg-gray-50 opacity-60;
}
</style>